public inbox for git-commits@fedoraproject.org
help / color / mirror / Atom feed
* [rpms/qemu] eln: * Tue Jun 23 2026 Miroslav Rezanina <mrezanin@redhat.com> - 10.1.0-22
@ 2026-06-30 15:09 Miroslav Rezanina
0 siblings, 0 replies; only message in thread
From: Miroslav Rezanina @ 2026-06-30 15:09 UTC (permalink / raw)
To: git-commits
A new commit has been pushed.
Repo : rpms/qemu
Branch : eln
Commit : 169c46da5ca9a58fb3b73d687d63c287f5218116
Author : Miroslav Rezanina <mrezanin@redhat.com>
Date : 2026-06-30T17:07:57+02:00
Stats : +6194/-1 in 53 file(s)
URL : https://src.fedoraproject.org/rpms/qemu/c/169c46da5ca9a58fb3b73d687d63c287f5218116?branch=eln
Log:
* Tue Jun 23 2026 Miroslav Rezanina <mrezanin@redhat.com> - 10.1.0-22
- kvm-blkdebug-Add-delay-ns-option.patch [RHEL-121686]
- kvm-block-Add-blk_co_start-end_request-and-BDRV_REQ_NO_Q.patch [RHEL-121686]
- kvm-block-Add-flags-parameter-to-blk_-_pdiscard.patch [RHEL-121686]
- kvm-ide-Minimal-fix-for-deadlock-between-TRIM-and-drain.patch [RHEL-121686]
- kvm-ide-Clean-up-ide_trim_co_entry-to-be-idiomatic-corou.patch [RHEL-121686]
- kvm-ide-test-Factor-out-wait_dma_completion.patch [RHEL-121686]
- kvm-ide-test-Test-reset-during-TRIM.patch [RHEL-121686]
- kvm-block-graph-lock-fix-missed-wakeup-in-bdrv_graph_co_.patch [RHEL-186384]
- kvm-block-curl-fix-curl-internal-handles-handling.patch [RHEL-186384]
- kvm-block-curl.c-Use-explicit-long-constants-in-curl_eas.patch [RHEL-186384]
- kvm-block-curl.c-Fix-CURLOPT_VERBOSE-parameter-type.patch [RHEL-186384]
- kvm-block-curl-fix-concurrent-completion-handling.patch [RHEL-186384]
- kvm-block-curl-free-s-password-in-cleanup-paths.patch [RHEL-186384]
- kvm-nvme-Kick-and-check-completions-in-BDS-context.patch [RHEL-186384]
- kvm-nvme-Note-in-which-AioContext-some-functions-run.patch [RHEL-186384]
- kvm-block-remove-detached-header-option-from-opts-after-.patch [RHEL-186384]
- kvm-block-fix-luks-amend-when-run-in-coroutine.patch [RHEL-186384]
- kvm-qed-Don-t-try-to-flush-during-incoming-migration.patch [RHEL-186384]
- kvm-block-vmdk-fix-OOB-read-in-vmdk_read_extent.patch [RHEL-186384]
- kvm-block-throttle-groups-fix-deadlock-with-iolimits-and.patch [RHEL-186384]
- kvm-throttle-group-Fix-race-condition-in-throttle_group_.patch [RHEL-186384]
- kvm-qemu-img-Fix-amend-option-parse-error-handling.patch [RHEL-186384]
- kvm-qemu-img-rebase-don-t-exceed-IO_BUF_SIZE-in-one-oper.patch [RHEL-186384]
- kvm-python-backport-drop-Python3.6-workarounds.patch [RHEL-186384]
- kvm-python-backport-Remove-deprecated-get_event_loop-cal.patch [RHEL-186384]
- kvm-python-backport-avoid-creating-additional-event-loop.patch [RHEL-186384]
- kvm-iotests-147-ensure-temporary-sockets-are-closed-befo.patch [RHEL-186384]
- kvm-iotests-151-ensure-subprocesses-are-cleaned-up.patch [RHEL-186384]
- kvm-tests-qemu-iotest-fix-iotest-024-with-qed-images.patch [RHEL-186384]
- kvm-tests-qemu-iotests-Fix-check-for-existing-file-in-_r.patch [RHEL-186384]
- kvm-async-access-bottom-half-flags-with-qatomic_read.patch [RHEL-186384]
- kvm-block-linux-aio-bound-ioq_submit-recursion-depth.patch [RHEL-186384]
- kvm-block-io-fallback-to-bounce-buffer-if-BLKZEROOUT-is-.patch [RHEL-186384]
- kvm-file-posix-populate-pwrite_zeroes_alignment.patch [RHEL-186384]
- kvm-block-use-pwrite_zeroes_alignment-when-writing-first.patch [RHEL-186384]
- kvm-iotests-add-Linux-loop-device-image-creation-test.patch [RHEL-186384]
- kvm-virtio-Fix-crash-when-sriov-pf-is-set-for-non-PCI-Ex.patch [RHEL-186384]
- kvm-virtio-scsi-pass-the-same-cdb_size-to-virtio_scsi_po.patch [RHEL-186384]
- kvm-hw-scsi-avoid-deadlock-upon-TMF-request-cancelling-w.patch [RHEL-186384]
- kvm-virtio-blk-fix-zone-report-buffer-out-of-memory-CVE-.patch [RHEL-186384]
- kvm-ide-Fix-potential-assertion-failure-on-VM-stop-for-P.patch [RHEL-186384]
- kvm-block-Create-DEFAULT_BLOCK_CONF-macro.patch [RHEL-186384]
- kvm-block-Add-more-defaults-to-DEFAULT_BLOCK_CONF.patch [RHEL-186384]
- kvm-block-mirror-check-range-when-setting-zero-bitmap-fo.patch [RHEL-186384]
- kvm-iotests-test-active-mirror-with-unaligned-small-writ.patch [RHEL-186384]
- kvm-block-mirror-fix-assertion-failure-upon-duplicate-co.patch [RHEL-186384]
- kvm-commit-Drain-nodes-across-all-of-bdrv_commit.patch [RHEL-186384]
- kvm-qemu-io-Add-aio_discard-command.patch [RHEL-186384]
- kvm-qcow2-Fix-corruption-on-discard-during-write-with-CO.patch [RHEL-186384]
- kvm-iotests-046-Test-that-discard-write_zeroes-wait-for-.patch [RHEL-186384]
- kvm-qcow2-Fix-data-loss-on-zero-write-with-detect-zeroes.patch [RHEL-186384]
- kvm-block-Fix-crash-after-setting-latency-historygram-wi.patch [RHEL-186384]
- Resolves: RHEL-121686
(qemu-kvm hung during drain after double pause)
- Resolves: RHEL-186384
(virt-storage: Backport stable branch fixes)
---
diff --git a/kvm-async-access-bottom-half-flags-with-qatomic_read.patch b/kvm-async-access-bottom-half-flags-with-qatomic_read.patch
new file mode 100644
index 0000000..efb742e
--- /dev/null
+++ b/kvm-async-access-bottom-half-flags-with-qatomic_read.patch
@@ -0,0 +1,69 @@
+From 649398182d4897325f37cee57f81ae9f22272d66 Mon Sep 17 00:00:00 2001
+From: Paolo Bonzini <pbonzini@redhat.com>
+Date: Mon, 13 Oct 2025 18:24:54 +0200
+Subject: [PATCH 31/52] async: access bottom half flags with qatomic_read
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
+RH-Jira: RHEL-186384
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
+RH-Commit: [24/45] c2367e9cbd60ef60a2a2a61cda56c31f4bd9a00b (kmwolf/centos-qemu-kvm)
+
+Running test-aio-multithread under TSAN reveals data races on bh->flags.
+Because bottom halves may be scheduled or canceled asynchronously,
+without taking a lock, adjust aio_compute_bh_timeout() and aio_ctx_check()
+to use a relaxed read to access the flags.
+
+Use an acquire load to ensure that anything that was written prior to
+qemu_bh_schedule() is visible.
+
+Closes: https://gitlab.com/qemu-project/qemu/-/issues/2749
+Closes: https://gitlab.com/qemu-project/qemu/-/issues/851
+Cc: qemu-stable@nongnu.org
+Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
+(cherry picked from commit 5142397c79330aab9bef3230991c8ac0c251110f)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ util/async.c | 11 +++++++----
+ 1 file changed, 7 insertions(+), 4 deletions(-)
+
+diff --git a/util/async.c b/util/async.c
+index 2719c629ae..a736d2cd0d 100644
+--- a/util/async.c
++++ b/util/async.c
+@@ -256,8 +256,9 @@ static int64_t aio_compute_bh_timeout(BHList *head, int timeout)
+ QEMUBH *bh;
+
+ QSLIST_FOREACH_RCU(bh, head, next) {
+- if ((bh->flags & (BH_SCHEDULED | BH_DELETED)) == BH_SCHEDULED) {
+- if (bh->flags & BH_IDLE) {
++ int flags = qatomic_load_acquire(&bh->flags);
++ if ((flags & (BH_SCHEDULED | BH_DELETED)) == BH_SCHEDULED) {
++ if (flags & BH_IDLE) {
+ /* idle bottom halves will be polled at least
+ * every 10ms */
+ timeout = 10000000;
+@@ -335,14 +336,16 @@ aio_ctx_check(GSource *source)
+ aio_notify_accept(ctx);
+
+ QSLIST_FOREACH_RCU(bh, &ctx->bh_list, next) {
+- if ((bh->flags & (BH_SCHEDULED | BH_DELETED)) == BH_SCHEDULED) {
++ int flags = qatomic_load_acquire(&bh->flags);
++ if ((flags & (BH_SCHEDULED | BH_DELETED)) == BH_SCHEDULED) {
+ return true;
+ }
+ }
+
+ QSIMPLEQ_FOREACH(s, &ctx->bh_slice_list, next) {
+ QSLIST_FOREACH_RCU(bh, &s->bh_list, next) {
+- if ((bh->flags & (BH_SCHEDULED | BH_DELETED)) == BH_SCHEDULED) {
++ int flags = qatomic_load_acquire(&bh->flags);
++ if ((flags & (BH_SCHEDULED | BH_DELETED)) == BH_SCHEDULED) {
+ return true;
+ }
+ }
+--
+2.52.0
+
diff --git a/kvm-blkdebug-Add-delay-ns-option.patch b/kvm-blkdebug-Add-delay-ns-option.patch
new file mode 100644
index 0000000..aa9a1cc
--- /dev/null
+++ b/kvm-blkdebug-Add-delay-ns-option.patch
@@ -0,0 +1,121 @@
+From 6386e658adc798b2af7400a53941523d21dd581d Mon Sep 17 00:00:00 2001
+From: Kevin Wolf <kwolf@redhat.com>
+Date: Tue, 21 Apr 2026 18:11:26 +0200
+Subject: [PATCH 01/52] blkdebug: Add 'delay-ns' option
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 503: ide: Fix deadlock between TRIM and drain
+RH-Jira: RHEL-121686
+RH-Acked-by: Hanna Czenczek <hreitz@redhat.com>
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Commit: [1/7] b6ade079279909f3b38a03fc710c1f0ebd475915 (kmwolf/centos-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 <kwolf@redhat.com>
+Message-ID: <20260421161132.99878-2-kwolf@redhat.com>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+(cherry picked from commit d5e4090177ad382e01084a1594a1a60a69f4c1cd)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ 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..86647e0
--- /dev/null
+++ b/kvm-block-Add-blk_co_start-end_request-and-BDRV_REQ_NO_Q.patch
@@ -0,0 +1,203 @@
+From c452d113b1d71de097e3b87f7d40d6865731b275 Mon Sep 17 00:00:00 2001
+From: Kevin Wolf <kwolf@redhat.com>
+Date: Tue, 21 Apr 2026 18:11:27 +0200
+Subject: [PATCH 02/52] block: Add blk_co_start/end_request() and
+ BDRV_REQ_NO_QUEUE
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 503: ide: Fix deadlock between TRIM and drain
+RH-Jira: RHEL-121686
+RH-Acked-by: Hanna Czenczek <hreitz@redhat.com>
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Commit: [2/7] 4053fabc93d73e2c7143763fa2676c7be7ad249d (kmwolf/centos-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 <kwolf@redhat.com>
+Message-ID: <20260421161132.99878-3-kwolf@redhat.com>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+(cherry picked from commit 34a67637767d3ed1ac813c44effe827bbfba5996)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ 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..f69bc48
--- /dev/null
+++ b/kvm-block-Add-flags-parameter-to-blk_-_pdiscard.patch
@@ -0,0 +1,174 @@
+From 9a2d6f97d94f24ad7bfc83aea06d3324cbd46424 Mon Sep 17 00:00:00 2001
+From: Kevin Wolf <kwolf@redhat.com>
+Date: Tue, 21 Apr 2026 18:11:28 +0200
+Subject: [PATCH 03/52] block: Add flags parameter to blk_*_pdiscard()
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 503: ide: Fix deadlock between TRIM and drain
+RH-Jira: RHEL-121686
+RH-Acked-by: Hanna Czenczek <hreitz@redhat.com>
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Commit: [3/7] 40ce25b1169cccc9c655eeb59827f8bf7daf75f4 (kmwolf/centos-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 <kwolf@redhat.com>
+Message-ID: <20260421161132.99878-4-kwolf@redhat.com>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+(cherry picked from commit 53074ba0330ae8831abbae2521c012e1d9072ed3)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ 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 acec0487a8..bd103a8840 100644
+--- a/nbd/server.c
++++ b/nbd/server.c
+@@ -2984,7 +2984,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-Add-more-defaults-to-DEFAULT_BLOCK_CONF.patch b/kvm-block-Add-more-defaults-to-DEFAULT_BLOCK_CONF.patch
new file mode 100644
index 0000000..7cf51a8
--- /dev/null
+++ b/kvm-block-Add-more-defaults-to-DEFAULT_BLOCK_CONF.patch
@@ -0,0 +1,56 @@
+From 748bbe57c3467eae94de533fb4099e89b8f52cc9 Mon Sep 17 00:00:00 2001
+From: Kevin Wolf <kwolf@redhat.com>
+Date: Fri, 10 Apr 2026 17:23:14 +0200
+Subject: [PATCH 43/52] block: Add more defaults to DEFAULT_BLOCK_CONF
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
+RH-Jira: RHEL-186384
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
+RH-Commit: [36/45] 47d0e6be200b5f5a481d43a1304c3e0f5ce6b973 (kmwolf/centos-qemu-kvm)
+
+discard_granularity was missing from this, which means that SCSI disks
+created with -drive if=scsi would default to 0 (i.e. disabling discards)
+instead of -1, which makes scsi-hd automatically pick a granularity and
+is the default of the corresponding qdev property for -device scsi-hd.
+
+This was broken in QEMU 9.0 with commit 3089637.
+
+Also set other fields whose default isn't an obvious 0. These are not
+actual bug fixes because ON_OFF_AUTO_AUTO in fact happens to be 0, but
+it's better not to rely on the order of enums.
+
+Cc: qemu-stable@nongnu.org
+Fixes: 308963746169 ('scsi: Don't ignore most usb-storage properties')
+Reported-by: Lexi Winter <ivy@FreeBSD.org>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+Message-ID: <20260410152314.86412-3-kwolf@redhat.com>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+(cherry picked from commit f27aea1896338f4dd085a0e2cb2ab3797c5fe3e9)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ include/hw/block/block.h | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+diff --git a/include/hw/block/block.h b/include/hw/block/block.h
+index 7da643faff..9225e75925 100644
+--- a/include/hw/block/block.h
++++ b/include/hw/block/block.h
+@@ -53,7 +53,12 @@ static inline unsigned int get_physical_block_exp(BlockConf *conf)
+
+ #define DEFAULT_BLOCK_CONF (BlockConf) { \
+ .bootindex = -1, \
++ .backend_defaults = ON_OFF_AUTO_AUTO, \
++ .discard_granularity = -1, \
++ .wce = ON_OFF_AUTO_AUTO, \
+ .share_rw = false, \
++ .account_invalid = ON_OFF_AUTO_AUTO, \
++ .account_failed = ON_OFF_AUTO_AUTO, \
+ .rerror = BLOCKDEV_ON_ERROR_AUTO, \
+ .werror = BLOCKDEV_ON_ERROR_AUTO, \
+ }
+--
+2.52.0
+
diff --git a/kvm-block-Create-DEFAULT_BLOCK_CONF-macro.patch b/kvm-block-Create-DEFAULT_BLOCK_CONF-macro.patch
new file mode 100644
index 0000000..5bdab59
--- /dev/null
+++ b/kvm-block-Create-DEFAULT_BLOCK_CONF-macro.patch
@@ -0,0 +1,68 @@
+From d3c16be7233df4e4103cb048f392d4cb1ffa6e53 Mon Sep 17 00:00:00 2001
+From: Kevin Wolf <kwolf@redhat.com>
+Date: Fri, 10 Apr 2026 17:23:13 +0200
+Subject: [PATCH 42/52] block: Create DEFAULT_BLOCK_CONF macro
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
+RH-Jira: RHEL-186384
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
+RH-Commit: [35/45] 8794cac427709a1462918bad08b4f135911b3c1b (kmwolf/centos-qemu-kvm)
+
+The property default values from include/hw/block/block.h were
+duplicated in scsi_bus_legacy_handle_cmdline(), allowing them to go out
+of sync easily. There doesn't seem a good way to avoid the duplication,
+but moving them next to each other in the header file should help to
+avoid this problem in the future.
+
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+Message-ID: <20260410152314.86412-2-kwolf@redhat.com>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+(cherry picked from commit a1310cc6281d22ac948f4aa198dcc55d58fc039d)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ hw/scsi/scsi-bus.c | 7 +------
+ include/hw/block/block.h | 7 +++++++
+ 2 files changed, 8 insertions(+), 6 deletions(-)
+
+diff --git a/hw/scsi/scsi-bus.c b/hw/scsi/scsi-bus.c
+index 878ccf62c9..88373d957f 100644
+--- a/hw/scsi/scsi-bus.c
++++ b/hw/scsi/scsi-bus.c
+@@ -485,12 +485,7 @@ void scsi_bus_legacy_handle_cmdline(SCSIBus *bus)
+ Location loc;
+ DriveInfo *dinfo;
+ int unit;
+- BlockConf conf = {
+- .bootindex = -1,
+- .share_rw = false,
+- .rerror = BLOCKDEV_ON_ERROR_AUTO,
+- .werror = BLOCKDEV_ON_ERROR_AUTO,
+- };
++ BlockConf conf = DEFAULT_BLOCK_CONF;
+
+ loc_push_none(&loc);
+ for (unit = 0; unit <= bus->info->max_target; unit++) {
+diff --git a/include/hw/block/block.h b/include/hw/block/block.h
+index b4d914624e..7da643faff 100644
+--- a/include/hw/block/block.h
++++ b/include/hw/block/block.h
+@@ -51,6 +51,13 @@ static inline unsigned int get_physical_block_exp(BlockConf *conf)
+ return exp;
+ }
+
++#define DEFAULT_BLOCK_CONF (BlockConf) { \
++ .bootindex = -1, \
++ .share_rw = false, \
++ .rerror = BLOCKDEV_ON_ERROR_AUTO, \
++ .werror = BLOCKDEV_ON_ERROR_AUTO, \
++}
++
+ #define DEFINE_BLOCK_PROPERTIES_BASE(_state, _conf) \
+ DEFINE_PROP_ON_OFF_AUTO("backend_defaults", _state, \
+ _conf.backend_defaults, ON_OFF_AUTO_AUTO), \
+--
+2.52.0
+
diff --git a/kvm-block-Fix-crash-after-setting-latency-historygram-wi.patch b/kvm-block-Fix-crash-after-setting-latency-historygram-wi.patch
new file mode 100644
index 0000000..69e1ab5
--- /dev/null
+++ b/kvm-block-Fix-crash-after-setting-latency-historygram-wi.patch
@@ -0,0 +1,65 @@
+From a694d76d085f091f21d5062c4c0328db7b5eb188 Mon Sep 17 00:00:00 2001
+From: Kevin Wolf <kwolf@redhat.com>
+Date: Tue, 31 Mar 2026 12:26:08 +0200
+Subject: [PATCH 52/52] block: Fix crash after setting latency historygram with
+ single bin
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
+RH-Jira: RHEL-186384
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
+RH-Commit: [45/45] c604a01f5c30539af7097ee14f49dfdb22a83b2d (kmwolf/centos-qemu-kvm)
+
+Passing an empty list of boundaries to block-latency-histogram-set sets
+up a state that leads to a NULL pointer dereference when the next
+request should be accounted for. This is not a useful configuration, so
+just error out if the user tries to set it.
+
+The crash can easily be reproduced with the following script:
+
+ qmp() {
+ cat <<EOF
+ {'execute':'qmp_capabilities'}
+ {'execute':'block-latency-histogram-set',
+ 'arguments': {'id':'ide0','boundaries':[]}}
+ {'execute':'cont'}
+ EOF
+ }
+
+ qmp | ./qemu-system-x86_64 -S -qmp stdio \
+ -drive if=none,format=raw,file=null-co:// \
+ -device ide-hd,drive=none0,id=ide0
+
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+Message-ID: <20260331102608.60882-1-kwolf@redhat.com>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+(cherry picked from commit a55402d5c3a8c63c801de86896f86c9abeda0ca8)
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ block/accounting.c | 9 +++++++++
+ 1 file changed, 9 insertions(+)
+
+diff --git a/block/accounting.c b/block/accounting.c
+index 5cf51f029b..f00fe99740 100644
+--- a/block/accounting.c
++++ b/block/accounting.c
+@@ -185,6 +185,15 @@ int block_latency_histogram_set(BlockAcctStats *stats, enum BlockAcctType type,
+ prev = entry->value;
+ }
+
++ /*
++ * block_latency_histogram_account() assumes that it can always access
++ * hist->boundaries[0], so require at least one boundary. A histogram with
++ * a single bin is useless anyway.
++ */
++ if (new_nbins <= 1) {
++ return -EINVAL;
++ }
++
+ hist->nbins = new_nbins;
+ g_free(hist->boundaries);
+ hist->boundaries = g_new(uint64_t, hist->nbins - 1);
+--
+2.52.0
+
diff --git a/kvm-block-curl-fix-concurrent-completion-handling.patch b/kvm-block-curl-fix-concurrent-completion-handling.patch
new file mode 100644
index 0000000..6f70497
--- /dev/null
+++ b/kvm-block-curl-fix-concurrent-completion-handling.patch
@@ -0,0 +1,71 @@
+From 974fa918950f21b9ee6134af75c551fbc9a61905 Mon Sep 17 00:00:00 2001
+From: Antoine Damhet <adamhet@scaleway.com>
+Date: Thu, 12 Feb 2026 17:27:24 +0100
+Subject: [PATCH 12/52] block/curl: fix concurrent completion handling
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
+RH-Jira: RHEL-186384
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
+RH-Commit: [5/45] c2be04a98f68f562b5cf702572acf31a361934ed (kmwolf/centos-qemu-kvm)
+
+curl_multi_check_completion would bail upon the first completed
+transfer even if more completion messages were available thus leaving
+some in flight IOs stuck.
+
+Rework a bit the loop to make the iterations clearer and drop the breaks.
+
+The original hang can be somewhat reproduced with the following command:
+
+$ qemu-img convert -p -m 16 -O qcow2 -c --image-opts \
+ 'file.driver=https,file.url=https://scaleway.testdebit.info/10G.iso,file.readahead=1M' \
+ /tmp/test.qcow2
+
+Fixes: 1f2cead32443 ("curl: Ensure all informationals are checked for completion")
+Cc: qemu-stable@nongnu.org
+Signed-off-by: Antoine Damhet <adamhet@scaleway.com>
+Message-ID: <20260212162730.440855-2-adamhet@scaleway.com>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+(cherry picked from commit 6f7b0a23a6ea0cc72ad222ab37936248d99d4256)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ block/curl.c | 11 ++---------
+ 1 file changed, 2 insertions(+), 9 deletions(-)
+
+diff --git a/block/curl.c b/block/curl.c
+index 96498aac1d..dabd2a905e 100644
+--- a/block/curl.c
++++ b/block/curl.c
+@@ -324,17 +324,11 @@ curl_find_buf(BDRVCURLState *s, uint64_t start, uint64_t len, CURLAIOCB *acb)
+ static void curl_multi_check_completion(BDRVCURLState *s)
+ {
+ int msgs_in_queue;
++ CURLMsg *msg;
+
+ /* Try to find done transfers, so we can free the easy
+ * handle again. */
+- for (;;) {
+- CURLMsg *msg;
+- msg = curl_multi_info_read(s->multi, &msgs_in_queue);
+-
+- /* Quit when there are no more completions */
+- if (!msg)
+- break;
+-
++ while ((msg = curl_multi_info_read(s->multi, &msgs_in_queue))) {
+ if (msg->msg == CURLMSG_DONE) {
+ int i;
+ CURLState *state = NULL;
+@@ -397,7 +391,6 @@ static void curl_multi_check_completion(BDRVCURLState *s)
+ }
+
+ curl_clean_state(state);
+- break;
+ }
+ }
+ }
+--
+2.52.0
+
diff --git a/kvm-block-curl-fix-curl-internal-handles-handling.patch b/kvm-block-curl-fix-curl-internal-handles-handling.patch
new file mode 100644
index 0000000..73aa175
--- /dev/null
+++ b/kvm-block-curl-fix-curl-internal-handles-handling.patch
@@ -0,0 +1,71 @@
+From e61f10f2711f4fee1c4ef0611b89d85cf3141f26 Mon Sep 17 00:00:00 2001
+From: Michael Tokarev <mjt@tls.msk.ru>
+Date: Sun, 24 Aug 2025 03:05:32 +0300
+Subject: [PATCH 09/52] block/curl: fix curl internal handles handling
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
+RH-Jira: RHEL-186384
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
+RH-Commit: [2/45] adce90f28e968783f18a66f44e42f8023b46b0e1 (kmwolf/centos-qemu-kvm)
+
+block/curl.c uses CURLMOPT_SOCKETFUNCTION to register a socket callback.
+According to the documentation, this callback is called not just with
+application-created sockets but also with internal curl sockets, - and
+for such sockets, user data pointer is not set by the application, so
+the result qemu crashing.
+
+Pass BDRVCURLState directly to the callback function as user pointer,
+instead of relying on CURLINFO_PRIVATE.
+
+This problem started happening with update of libcurl from 8.9 to 8.10 --
+apparently with this change curl started using private handles more.
+
+(CURLINFO_PRIVATE is used in one more place, in curl_multi_check_completion() -
+it might need a similar fix too)
+
+Resolves: https://gitlab.com/qemu-project/qemu/-/issues/3081
+Cc: qemu-stable@qemu.org
+Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+(cherry picked from commit 606978500c3d18fb89a49844f253097b17f757de)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ block/curl.c | 7 ++-----
+ 1 file changed, 2 insertions(+), 5 deletions(-)
+
+diff --git a/block/curl.c b/block/curl.c
+index d69bcdff79..74ec62d9d8 100644
+--- a/block/curl.c
++++ b/block/curl.c
+@@ -162,13 +162,9 @@ static int curl_timer_cb(CURLM *multi, long timeout_ms, void *opaque)
+ static int curl_sock_cb(CURL *curl, curl_socket_t fd, int action,
+ void *userp, void *sp)
+ {
+- BDRVCURLState *s;
+- CURLState *state = NULL;
++ BDRVCURLState *s = userp;
+ CURLSocket *socket;
+
+- curl_easy_getinfo(curl, CURLINFO_PRIVATE, (char **)&state);
+- s = state->s;
+-
+ socket = g_hash_table_lookup(s->sockets, GINT_TO_POINTER(fd));
+ if (!socket) {
+ socket = g_new0(CURLSocket, 1);
+@@ -619,6 +615,7 @@ static void curl_attach_aio_context(BlockDriverState *bs,
+ assert(!s->multi);
+ s->multi = curl_multi_init();
+ s->aio_context = new_context;
++ curl_multi_setopt(s->multi, CURLMOPT_SOCKETDATA, s);
+ curl_multi_setopt(s->multi, CURLMOPT_SOCKETFUNCTION, curl_sock_cb);
+ curl_multi_setopt(s->multi, CURLMOPT_TIMERDATA, s);
+ curl_multi_setopt(s->multi, CURLMOPT_TIMERFUNCTION, curl_timer_cb);
+--
+2.52.0
+
diff --git a/kvm-block-curl-free-s-password-in-cleanup-paths.patch b/kvm-block-curl-free-s-password-in-cleanup-paths.patch
new file mode 100644
index 0000000..6037434
--- /dev/null
+++ b/kvm-block-curl-free-s-password-in-cleanup-paths.patch
@@ -0,0 +1,57 @@
+From 36b96d00d6e51e02eca8bd2b4a11536376187f54 Mon Sep 17 00:00:00 2001
+From: GuoHan Zhao <zhaoguohan@kylinos.cn>
+Date: Fri, 20 Mar 2026 14:30:16 +0800
+Subject: [PATCH 13/52] block/curl: free s->password in cleanup paths
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
+RH-Jira: RHEL-186384
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
+RH-Commit: [6/45] 8c30ddfca7d21b63120d66714cf653bd39cdb611 (kmwolf/centos-qemu-kvm)
+
+When password-secret is used, curl_open() resolves it with
+qcrypto_secret_lookup_as_utf8() and stores the returned buffer in
+s->password.
+
+Unlike s->proxypassword, s->password is not freed either in the open
+failure path or in curl_close(), so the resolved secret leaks once it
+has been allocated.
+
+Free s->password in both cleanup paths.
+
+Fixes: 1bff96064290 ('curl: add support for HTTP authentication parameters')
+Signed-off-by: GuoHan Zhao <zhaoguohan@kylinos.cn>
+Message-ID: <20260320063016.262954-1-zhaoguohan_salmon@163.com>
+Reviewed-by: Kevin Wolf <kwolf@redhat.com>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+(cherry picked from commit 51fc8443c122fedf4d4891bbc3a1ff25dd8bacdf)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ block/curl.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/block/curl.c b/block/curl.c
+index dabd2a905e..aabb602d9b 100644
+--- a/block/curl.c
++++ b/block/curl.c
+@@ -876,6 +876,7 @@ out_noclean:
+ g_free(s->cookie);
+ g_free(s->url);
+ g_free(s->username);
++ g_free(s->password);
+ g_free(s->proxyusername);
+ g_free(s->proxypassword);
+ if (s->sockets) {
+@@ -987,6 +988,7 @@ static void curl_close(BlockDriverState *bs)
+ g_free(s->cookie);
+ g_free(s->url);
+ g_free(s->username);
++ g_free(s->password);
+ g_free(s->proxyusername);
+ g_free(s->proxypassword);
+ }
+--
+2.52.0
+
diff --git a/kvm-block-curl.c-Fix-CURLOPT_VERBOSE-parameter-type.patch b/kvm-block-curl.c-Fix-CURLOPT_VERBOSE-parameter-type.patch
new file mode 100644
index 0000000..8a6fa17
--- /dev/null
+++ b/kvm-block-curl.c-Fix-CURLOPT_VERBOSE-parameter-type.patch
@@ -0,0 +1,44 @@
+From e3921ea32fdc43a74a639e215a861e464ec3da80 Mon Sep 17 00:00:00 2001
+From: "Richard W.M. Jones" <rjones@redhat.com>
+Date: Mon, 13 Oct 2025 13:41:19 +0100
+Subject: [PATCH 11/52] block/curl.c: Fix CURLOPT_VERBOSE parameter type
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
+RH-Jira: RHEL-186384
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
+RH-Commit: [4/45] 7adffe8d6de2464ed632918f3d8b4200ada1f2a0 (kmwolf/centos-qemu-kvm)
+
+In commit ed26056d90 ("block/curl.c: Use explicit long constants in
+curl_easy_setopt calls") we missed a further call that takes a long
+parameter.
+
+Reported-by: Kevin Wolf <kwolf@redhat.com>
+Signed-off-by: Richard W.M. Jones <rjones@redhat.com>
+Message-ID: <20251013124127.604401-1-rjones@redhat.com>
+Reviewed-by: Kevin Wolf <kwolf@redhat.com>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+(cherry picked from commit ad97769e9dcf4dbdaae6d859176e5f37fd6a7c66)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ block/curl.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/block/curl.c b/block/curl.c
+index bbe891dafb..96498aac1d 100644
+--- a/block/curl.c
++++ b/block/curl.c
+@@ -538,7 +538,7 @@ static int curl_init_state(BDRVCURLState *s, CURLState *state)
+ #endif
+
+ #ifdef DEBUG_VERBOSE
+- if (curl_easy_setopt(state->curl, CURLOPT_VERBOSE, 1)) {
++ if (curl_easy_setopt(state->curl, CURLOPT_VERBOSE, 1L)) {
+ goto err;
+ }
+ #endif
+--
+2.52.0
+
diff --git a/kvm-block-curl.c-Use-explicit-long-constants-in-curl_eas.patch b/kvm-block-curl.c-Use-explicit-long-constants-in-curl_eas.patch
new file mode 100644
index 0000000..fa22b55
--- /dev/null
+++ b/kvm-block-curl.c-Use-explicit-long-constants-in-curl_eas.patch
@@ -0,0 +1,90 @@
+From a03e26bc3b51997e4584fe95c4da0feba85c076a Mon Sep 17 00:00:00 2001
+From: "Richard W.M. Jones" <rjones@redhat.com>
+Date: Thu, 9 Oct 2025 15:08:31 +0100
+Subject: [PATCH 10/52] block/curl.c: Use explicit long constants in
+ curl_easy_setopt calls
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
+RH-Jira: RHEL-186384
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
+RH-Commit: [3/45] 4e4b4f2c1704cc17a27ebca3c1a70bfc8dcd1ebd (kmwolf/centos-qemu-kvm)
+
+curl_easy_setopt takes a variable argument that depends on what
+CURLOPT you are setting. Some require a long constant. Passing a
+plain int constant is potentially wrong on some platforms.
+
+With warnings enabled, multiple warnings like this were printed:
+
+../block/curl.c: In function ‘curl_init_state’:
+../block/curl.c:474:13: warning: call to ‘_curl_easy_setopt_err_long’ declared with attribute warning: curl_easy_setopt expects a long argument [-Wattribute-warning]
+ 474 | curl_easy_setopt(state->curl, CURLOPT_AUTOREFERER, 1) ||
+ | ^
+
+Signed-off-by: Richard W.M. Jones <rjones@redhat.com>
+Signed-off-by: Chenxi Mao <maochenxi@bosc.ac.cn>
+Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
+Reviewed-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>
+Reviewed-by: Thomas Huth <thuth@redhat.com>
+Reviewed-by: Richard Henderson <richard.henderson@linaro.org>
+Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
+Message-ID: <20251009141026.4042021-2-rjones@redhat.com>
+(cherry picked from commit ed26056d90ddff21351f3efd2cb47fea4f0e1d45)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ block/curl.c | 10 +++++-----
+ contrib/elf2dmp/download.c | 4 ++--
+ 2 files changed, 7 insertions(+), 7 deletions(-)
+
+diff --git a/block/curl.c b/block/curl.c
+index 74ec62d9d8..bbe891dafb 100644
+--- a/block/curl.c
++++ b/block/curl.c
+@@ -485,11 +485,11 @@ static int curl_init_state(BDRVCURLState *s, CURLState *state)
+ (void *)curl_read_cb) ||
+ curl_easy_setopt(state->curl, CURLOPT_WRITEDATA, (void *)state) ||
+ curl_easy_setopt(state->curl, CURLOPT_PRIVATE, (void *)state) ||
+- curl_easy_setopt(state->curl, CURLOPT_AUTOREFERER, 1) ||
+- curl_easy_setopt(state->curl, CURLOPT_FOLLOWLOCATION, 1) ||
+- curl_easy_setopt(state->curl, CURLOPT_NOSIGNAL, 1) ||
++ curl_easy_setopt(state->curl, CURLOPT_AUTOREFERER, 1L) ||
++ curl_easy_setopt(state->curl, CURLOPT_FOLLOWLOCATION, 1L) ||
++ curl_easy_setopt(state->curl, CURLOPT_NOSIGNAL, 1L) ||
+ curl_easy_setopt(state->curl, CURLOPT_ERRORBUFFER, state->errmsg) ||
+- curl_easy_setopt(state->curl, CURLOPT_FAILONERROR, 1)) {
++ curl_easy_setopt(state->curl, CURLOPT_FAILONERROR, 1L)) {
+ goto err;
+ }
+ if (s->username) {
+@@ -814,7 +814,7 @@ static int curl_open(BlockDriverState *bs, QDict *options, int flags,
+ }
+
+ s->accept_range = false;
+- if (curl_easy_setopt(state->curl, CURLOPT_NOBODY, 1) ||
++ if (curl_easy_setopt(state->curl, CURLOPT_NOBODY, 1L) ||
+ curl_easy_setopt(state->curl, CURLOPT_HEADERFUNCTION, curl_header_cb) ||
+ curl_easy_setopt(state->curl, CURLOPT_HEADERDATA, s)) {
+ pstrcpy(state->errmsg, CURL_ERROR_SIZE,
+diff --git a/contrib/elf2dmp/download.c b/contrib/elf2dmp/download.c
+index 21306b3fd4..fa8da0f9a2 100644
+--- a/contrib/elf2dmp/download.c
++++ b/contrib/elf2dmp/download.c
+@@ -27,8 +27,8 @@ bool download_url(const char *name, const char *url)
+ if (curl_easy_setopt(curl, CURLOPT_URL, url) != CURLE_OK
+ || curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, NULL) != CURLE_OK
+ || curl_easy_setopt(curl, CURLOPT_WRITEDATA, file) != CURLE_OK
+- || curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1) != CURLE_OK
+- || curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0) != CURLE_OK
++ || curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L) != CURLE_OK
++ || curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L) != CURLE_OK
+ || curl_easy_perform(curl) != CURLE_OK) {
+ unlink(name);
+ fclose(file);
+--
+2.52.0
+
diff --git a/kvm-block-fix-luks-amend-when-run-in-coroutine.patch b/kvm-block-fix-luks-amend-when-run-in-coroutine.patch
new file mode 100644
index 0000000..9b3645b
--- /dev/null
+++ b/kvm-block-fix-luks-amend-when-run-in-coroutine.patch
@@ -0,0 +1,131 @@
+From fa0ed7060fac1b81af1f4b4390ad3f4ddbd03270 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= <berrange@redhat.com>
+Date: Fri, 19 Sep 2025 12:22:13 +0100
+Subject: [PATCH 17/52] block: fix luks 'amend' when run in coroutine
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
+RH-Jira: RHEL-186384
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
+RH-Commit: [10/45] 3dedc21108a95bebdc893820adfb19c2b88189cc (kmwolf/centos-qemu-kvm)
+
+Launch QEMU with
+
+ $ qemu-img create \
+ --object secret,id=sec0,data=123456 \
+ -f luks -o key-secret=sec0 demo.luks 1g
+
+ $ qemu-system-x86_64 \
+ --object secret,id=sec0,data=123456 \
+ -blockdev driver=luks,key-secret=sec0,file.filename=demo.luks,file.driver=file,node-name=luks
+
+Then in QMP shell attempt
+
+ x-blockdev-amend job-id=fish node-name=luks options={'state':'active','new-secret':'sec0','driver':'luks'}
+
+It will result in an assertion
+
+ #0 __pthread_kill_implementation (threadid=<optimized out>, signo=signo@entry=6, no_tid=no_tid@entry=0) at pthread_kill.c:44
+ #1 0x00007fad18b73f63 in __pthread_kill_internal (threadid=<optimized out>, signo=6) at pthread_kill.c:89
+ #2 0x00007fad18b19f3e in __GI_raise (sig=sig@entry=6) at ../sysdeps/posix/raise.c:26
+ #3 0x00007fad18b016d0 in __GI_abort () at abort.c:77
+ #4 0x00007fad18b01639 in __assert_fail_base
+ (fmt=<optimized out>, assertion=<optimized out>, file=<optimized out>, line=<optimized out>, function=<optimized out>) at assert.c:118
+ #5 0x00007fad18b120af in __assert_fail (assertion=<optimized out>, file=<optimized out>, line=<optimized out>, function=<optimized out>)
+ at assert.c:127
+ #6 0x000055ff74fdbd46 in bdrv_graph_rdlock_main_loop () at ../block/graph-lock.c:260
+ #7 0x000055ff7548521b in graph_lockable_auto_lock_mainloop (x=<optimized out>)
+ at /usr/src/debug/qemu-9.2.4-1.fc42.x86_64/include/block/graph-lock.h:266
+ #8 block_crypto_read_func (block=<optimized out>, offset=4096, buf=0x55ffb6d66ef0 "", buflen=256000, opaque=0x55ffb5edcc30, errp=0x55ffb6f00700)
+ at ../block/crypto.c:71
+ #9 0x000055ff75439f8b in qcrypto_block_luks_load_key
+ (block=block@entry=0x55ffb5edbe90, slot_idx=slot_idx@entry=0, password=password@entry=0x55ffb67dc260 "123456", masterkey=masterkey@entry=0x55ffb5fb0c40 "", readfunc=readfunc@entry=0x55ff754851e0 <block_crypto_read_func>, opaque=opaque@entry=0x55ffb5edcc30, errp=0x55ffb6f00700)
+ at ../crypto/block-luks.c:927
+ #10 0x000055ff7543b90f in qcrypto_block_luks_find_key
+ (block=<optimized out>, password=<optimized out>, masterkey=<optimized out>, readfunc=<optimized out>, opaque=<optimized out>, errp=<optimized out>) at ../crypto/block-luks.c:1045
+ #11 qcrypto_block_luks_amend_add_keyslot
+ (block=0x55ffb5edbe90, readfunc=0x55ff754851e0 <block_crypto_read_func>, writefunc=0x55ff75485100 <block_crypto_write_func>, opaque=0x55ffb5edcc3, opts_luks=0x7fad1715aef8, force=<optimized out>, errp=0x55ffb6f00700) at ../crypto/block-luks.c:1673
+ #12 qcrypto_block_luks_amend_options
+ (block=0x55ffb5edbe90, readfunc=0x55ff754851e0 <block_crypto_read_func>, writefunc=0x55ff75485100 <block_crypto_write_func>, opaque=0x55ffb5edcc30, options=0x7fad1715aef0, force=<optimized out>, errp=0x55ffb6f00700) at ../crypto/block-luks.c:1865
+ #13 0x000055ff75485b95 in block_crypto_amend_options_generic_luks
+ (bs=<optimized out>, amend_options=<optimized out>, force=<optimized out>, errp=<optimized out>) at ../block/crypto.c:949
+ #14 0x000055ff75485c28 in block_crypto_co_amend_luks (bs=<optimized out>, opts=<optimized out>, force=<optimized out>, errp=<optimized out>)
+ at ../block/crypto.c:1008
+ #15 0x000055ff754778e5 in blockdev_amend_run (job=0x55ffb6f00640, errp=0x55ffb6f00700) at ../block/amend.c:52
+ #16 0x000055ff75468b90 in job_co_entry (opaque=0x55ffb6f00640) at ../job.c:1106
+ #17 0x000055ff755a0fc2 in coroutine_trampoline (i0=<optimized out>, i1=<optimized out>) at ../util/coroutine-ucontext.c:175
+
+This changes the read/write callbacks to not assert that they
+are run in mainloop context if already in a coroutine.
+
+This is also reproduced by qemu-iotests cases 295 and 296.
+
+Fixes: 1f051dcbdf2e4b6f518db731c84e304b2b9d15ce
+Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
+Message-ID: <20250919112213.1530079-1-berrange@redhat.com>
+Reviewed-by: Kevin Wolf <kwolf@redhat.com>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+(cherry picked from commit c86488abaf017ed3f5a636c3247cd640a93d3b08)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ block/crypto.c | 30 ++++++++++++++++++++++--------
+ 1 file changed, 22 insertions(+), 8 deletions(-)
+
+diff --git a/block/crypto.c b/block/crypto.c
+index 17b4749a1e..7c37b23e36 100644
+--- a/block/crypto.c
++++ b/block/crypto.c
+@@ -67,11 +67,18 @@ static int block_crypto_read_func(QCryptoBlock *block,
+ BlockCrypto *crypto = bs->opaque;
+ ssize_t ret;
+
+- GLOBAL_STATE_CODE();
+- GRAPH_RDLOCK_GUARD_MAINLOOP();
++ if (qemu_in_coroutine()) {
++ GRAPH_RDLOCK_GUARD();
+
+- ret = bdrv_pread(crypto->header ? crypto->header : bs->file,
+- offset, buflen, buf, 0);
++ ret = bdrv_co_pread(crypto->header ? crypto->header : bs->file,
++ offset, buflen, buf, 0);
++ } else {
++ GLOBAL_STATE_CODE();
++ GRAPH_RDLOCK_GUARD_MAINLOOP();
++
++ ret = bdrv_pread(crypto->header ? crypto->header : bs->file,
++ offset, buflen, buf, 0);
++ }
+ if (ret < 0) {
+ error_setg_errno(errp, -ret, "Could not read encryption header");
+ return ret;
+@@ -90,11 +97,18 @@ static int block_crypto_write_func(QCryptoBlock *block,
+ BlockCrypto *crypto = bs->opaque;
+ ssize_t ret;
+
+- GLOBAL_STATE_CODE();
+- GRAPH_RDLOCK_GUARD_MAINLOOP();
++ if (qemu_in_coroutine()) {
++ GRAPH_RDLOCK_GUARD();
+
+- ret = bdrv_pwrite(crypto->header ? crypto->header : bs->file,
+- offset, buflen, buf, 0);
++ ret = bdrv_co_pwrite(crypto->header ? crypto->header : bs->file,
++ offset, buflen, buf, 0);
++ } else {
++ GLOBAL_STATE_CODE();
++ GRAPH_RDLOCK_GUARD_MAINLOOP();
++
++ ret = bdrv_pwrite(crypto->header ? crypto->header : bs->file,
++ offset, buflen, buf, 0);
++ }
+ if (ret < 0) {
+ error_setg_errno(errp, -ret, "Could not write encryption header");
+ return ret;
+--
+2.52.0
+
diff --git a/kvm-block-graph-lock-fix-missed-wakeup-in-bdrv_graph_co_.patch b/kvm-block-graph-lock-fix-missed-wakeup-in-bdrv_graph_co_.patch
new file mode 100644
index 0000000..72c1f9c
--- /dev/null
+++ b/kvm-block-graph-lock-fix-missed-wakeup-in-bdrv_graph_co_.patch
@@ -0,0 +1,107 @@
+From 319a9f69ecdd6bbe003503eb66aa4c30ffef3d40 Mon Sep 17 00:00:00 2001
+From: "Denis V. Lunev" <den@openvz.org>
+Date: Fri, 24 Apr 2026 12:39:16 +0200
+Subject: [PATCH 08/52] block/graph-lock: fix missed wakeup in
+ bdrv_graph_co_rdunlock()
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
+RH-Jira: RHEL-186384
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
+RH-Commit: [1/45] 03368780c08d1ee94cf412e6739c41c927259f9e (kmwolf/centos-qemu-kvm)
+
+tests/qemu-iotests/tests/iothreads-create reproduces the hang on
+master under `stress-ng --cpu $(nproc) --timeout 0`. The iotest's
+vm.run_job() times out and qemu stays permanently stuck in
+ppoll(timeout=-1) inside bdrv_graph_wrlock_drained -> blk_remove_bs
+during qemu_cleanup(). The timing window is narrow on modern
+bare-metal hardware and much wider in a VM guest; downstream trees
+that still use plain bdrv_graph_wrlock() in blk_remove_bs() hit it
+on the first iteration under the same stress.
+
+bdrv_graph_wrlock() zeroes has_writer around its AIO_WAIT_WHILE loop
+so that callbacks dispatched by aio_poll() can still take the read
+lock on the fast path. The rdunlock side, however, only kicks a
+waiting writer when has_writer is observed set; a reader that drops
+its lock inside the polling window silently returns and nothing ever
+wakes the writer:
+
+ main thread iothread0 coroutine
+ ----------- -------------------
+ bdrv_graph_wrlock: rdlock held, reader_count=1
+ bdrv_drain_all_begin_nopoll
+ has_writer = 0
+ AIO_WAIT_WHILE_UNLOCKED(
+ NULL, reader_count >= 1):
+ num_waiters++
+ smp_mb
+ aio_poll(main_ctx, true) --> bdrv_graph_co_rdunlock:
+ (ppoll, blocked) reader_count-- -> 0
+ smp_mb
+ read has_writer = 0
+ skip aio_wait_kick()
+ return
+
+reader_count is now 0 and num_waiters is still 1, but no BH, fd or
+timer on the main AioContext will fire -- the only entity that could
+kick just decided it did not have to. Main stays in ppoll() holding
+BQL, so RCU, VCPUs and any iothread path that needs BQL stall behind
+it. The hang is final; no timeout, no forward progress, no recovery
+as there is no other source of wake up inside qemu_cleanup().
+
+bdrv_drain_all_begin() does not close the race on its own: it
+quiesces in-flight I/O, but graph readers also include non-I/O
+coroutines (block-job cleanup, virtio-scsi polling) that drain does
+not evict. The bdrv_graph_wrlock_drained() wrapper narrows the
+window but does not eliminate it; every plain bdrv_graph_wrlock()
+site is exposed on the same basis.
+
+Drop the has_writer check in bdrv_graph_co_rdunlock() and call
+aio_wait_kick() unconditionally. The helper itself loads num_waiters
+atomically and only schedules a dummy BH when a waiter exists, so the
+change is a no-op on the no-writer path and closes the missed-wakeup
+on the writer path.
+
+Signed-off-by: Denis V. Lunev <den@openvz.org>
+Cc: Kevin Wolf <kwolf@redhat.com>
+Cc: Hanna Reitz <hreitz@redhat.com>
+Cc: Stefan Hajnoczi <stefanha@redhat.com>
+Cc: Fiona Ebner <f.ebner@proxmox.com>
+Message-ID: <20260424103917.248668-2-den@openvz.org>
+Reviewed-by: Kevin Wolf <kwolf@redhat.com>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+(cherry picked from commit e3082ab3b38538ebdbc5cd62b4c476b673c5e515)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ block/graph-lock.c | 12 +++++-------
+ 1 file changed, 5 insertions(+), 7 deletions(-)
+
+diff --git a/block/graph-lock.c b/block/graph-lock.c
+index b7319473a1..f2501d75fb 100644
+--- a/block/graph-lock.c
++++ b/block/graph-lock.c
+@@ -278,14 +278,12 @@ void coroutine_fn bdrv_graph_co_rdunlock(void)
+ smp_mb();
+
+ /*
+- * has_writer == 0: this means reader will read reader_count decreased
+- * has_writer == 1: we don't know if writer read reader_count old or
+- * new. Therefore, kick again so on next iteration
+- * writer will for sure read the updated value.
++ * Always kick: bdrv_graph_wrlock() zeroes has_writer while polling (to
++ * let callbacks take the reader lock via the fast path), so we cannot
++ * rely on has_writer to detect a waiting writer. aio_wait_kick() is a
++ * no-op when no one is waiting, so it is cheap in the common case.
+ */
+- if (qatomic_read(&has_writer)) {
+- aio_wait_kick();
+- }
++ aio_wait_kick();
+ }
+
+ void bdrv_graph_rdlock_main_loop(void)
+--
+2.52.0
+
diff --git a/kvm-block-io-fallback-to-bounce-buffer-if-BLKZEROOUT-is-.patch b/kvm-block-io-fallback-to-bounce-buffer-if-BLKZEROOUT-is-.patch
new file mode 100644
index 0000000..a78b853
--- /dev/null
+++ b/kvm-block-io-fallback-to-bounce-buffer-if-BLKZEROOUT-is-.patch
@@ -0,0 +1,66 @@
+From dfe7857ca3612b7c52b052735c8d1a96ecf1102d Mon Sep 17 00:00:00 2001
+From: Fiona Ebner <f.ebner@proxmox.com>
+Date: Sat, 13 Jun 2026 23:03:34 +0300
+Subject: [PATCH 33/52] block/io: fallback to bounce buffer if BLKZEROOUT is
+ not supported because of alignment
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
+RH-Jira: RHEL-186384
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
+RH-Commit: [26/45] 7a8351c003c1383c23e94f06617911b691cb7ec5 (kmwolf/centos-qemu-kvm)
+
+Commit 5634622bcb ("file-posix: allow BLKZEROOUT with -t writeback")
+enables the BLKZEROOUT ioctl when using 'writeback' cache, regressing
+certain 'qemu-img convert' invocations, because of a pre-existing
+issue. Namely, the BLKZEROOUT ioctl might fail with errno EINVAL when
+the request is shorter than the block size of the block device.
+Fallback to the bounce buffer, similar to when the ioctl is not
+supported at all, rather than treating such an error as fatal.
+
+Resolves: https://gitlab.com/qemu-project/qemu/-/issues/3257
+Resolves: https://bugzilla.proxmox.com/show_bug.cgi?id=7197
+Cc: qemu-stable@nongnu.org
+Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
+Message-ID: <20260105143416.737482-1-f.ebner@proxmox.com>
+[Added TODO comment describing a larger fix that could be implemented in
+the future.
+--Stefan]
+Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
+(cherry picked from commit b4e28c304bc58325f8f712cb25e5d700826caa25)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+
+Message-ID: <20260613200411.1808021-51-mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ block/io.c | 13 ++++++++++++-
+ 1 file changed, 12 insertions(+), 1 deletion(-)
+
+diff --git a/block/io.c b/block/io.c
+index 37df1e0253..84de5ab420 100644
+--- a/block/io.c
++++ b/block/io.c
+@@ -1920,7 +1920,18 @@ bdrv_co_do_pwrite_zeroes(BlockDriverState *bs, int64_t offset, int64_t bytes,
+ assert(!bs->supported_zero_flags);
+ }
+
+- if (ret == -ENOTSUP && !(flags & BDRV_REQ_NO_FALLBACK)) {
++ /*
++ * TODO The ret == -EINVAL && num < alignment case is a workaround for
++ * when request_alignment is 1 on files with cache=writeback. The Linux
++ * ioctl(BLKZEROOUT) requires block alignment and will fail with
++ * EINVAL. The block layer should align the request to
++ * write_zeroes_alignment instead of trying the syscall, failing, and
++ * falling back to a bounce buffer. Doing that is not easy so for now
++ * we use a bounce buffer:
++ * https://lore.kernel.org/qemu-devel/20260109120837.2772961-1-f.ebner@proxmox.com/
++ */
++ if ((ret == -ENOTSUP || (ret == -EINVAL && num < alignment)) &&
++ !(flags & BDRV_REQ_NO_FALLBACK)) {
+ /* Fall back to bounce buffer if write zeroes is unsupported */
+ BdrvRequestFlags write_flags = flags & ~BDRV_REQ_ZERO_WRITE;
+
+--
+2.52.0
+
diff --git a/kvm-block-linux-aio-bound-ioq_submit-recursion-depth.patch b/kvm-block-linux-aio-bound-ioq_submit-recursion-depth.patch
new file mode 100644
index 0000000..8051503
--- /dev/null
+++ b/kvm-block-linux-aio-bound-ioq_submit-recursion-depth.patch
@@ -0,0 +1,140 @@
+From 9a6c4bad7f575826796a4c690a0fa6bbcda1f5ad Mon Sep 17 00:00:00 2001
+From: "Denis V. Lunev" <den@openvz.org>
+Date: Sat, 13 Jun 2026 23:03:08 +0300
+Subject: [PATCH 32/52] block/linux-aio: bound ioq_submit() recursion depth
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
+RH-Jira: RHEL-186384
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
+RH-Commit: [25/45] 04545c714dacb2571f169b3da3c7cf493dea31bd (kmwolf/centos-qemu-kvm)
+
+qemu_laio_process_completions() wraps its body in defer_call_begin /
+defer_call_end. Inside the section, completion callbacks wake coroutines
+that queue new aiocbs; laio_do_submit() defers laio_deferred_fn. At the
+bottom of qemu_laio_process_completions() the defer_call_end() fires
+laio_deferred_fn, which calls ioq_submit(), closing the cycle:
+
+ ioq_submit
+ -> io_submit(2) // some sync completions
+ -> qemu_laio_process_completions // defer_call_begin
+ -> aio_co_wake // resumes coroutine
+ -> laio_do_submit
+ -> defer_call(laio_deferred_fn, s) // enqueued
+ -> defer_call_end // nesting drops to 0
+ -> laio_deferred_fn
+ -> ioq_submit // +1 stack frame, loop
+
+When io_submit(2) returns asynchronously (O_DIRECT) the cycle
+terminates in one extra frame: the fresh aiocb is still in flight, no
+completion is drained, no coroutine wakes, no new submission queues.
+When submissions complete synchronously (non-O_DIRECT, or per-descriptor
+drivers such as vmdk) each level enqueues more work for the next
+defer_call_end() to drain, so recursion grows without bound and QEMU
+crashes with SIGSEGV on the thread guard page.
+
+The cycle was closed by two performance commits, each correct in
+isolation:
+
+ 076682885d ("block/linux-aio: convert to blk_io_plug_call() API")
+ -- introduced laio_deferred_fn and wired
+ laio_do_submit -> defer_call(laio_deferred_fn, s).
+
+ 84d61e5f36 ("virtio: use defer_call() in virtio_irqfd_notify()")
+ -- added defer_call_begin/end around qemu_laio_process_completions
+ so virtio-irqfd notifications batch across a completion pass.
+
+The supported aio=native + cache=none pairing keeps submissions
+asynchronous, so the cycle stays bounded; nothing in the code enforces
+that contract. Observed in production as a SIGSEGV during a backup job
+configured with --cached + aio=native; reproducible on upstream with
+qemu-io against vmdk.
+
+Cap ioq_submit() recursion with a counter on LaioQueue, which is only
+accessed from the AioContext home thread. On overflow, return without
+submitting. The pending work is drained by s->completion_bh, which
+qemu_laio_process_completions() has already scheduled on entry -- no
+work is lost; one event-loop round-trip of latency is paid only when
+the bound is hit, which cannot happen on a supported configuration.
+
+Signed-off-by: Denis V. Lunev <den@openvz.org>
+CC: Kevin Wolf <kwolf@redhat.com>
+CC: Hanna Reitz <hreitz@redhat.com>
+CC: Stefan Hajnoczi <stefanha@redhat.com>
+CC: Paolo Bonzini <pbonzini@redhat.com>
+Message-ID: <20260520142503.251959-2-den@openvz.org>
+Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
+(cherry picked from commit 6864bec553b2e37699739615e604fc3c7bae0e1d)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+
+Message-ID: <20260613200411.1808021-25-mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ block/linux-aio.c | 22 ++++++++++++++++++++++
+ 1 file changed, 22 insertions(+)
+
+diff --git a/block/linux-aio.c b/block/linux-aio.c
+index 84397de54c..37de9b564b 100644
+--- a/block/linux-aio.c
++++ b/block/linux-aio.c
+@@ -36,6 +36,19 @@
+ /* Maximum number of requests in a batch. (default value) */
+ #define DEFAULT_MAX_BATCH 32
+
++/*
++ * Bound on how deep ioq_submit() may recurse on a single LaioQueue via the
++ * ioq_submit -> qemu_laio_process_completions -> defer_call_end ->
++ * laio_deferred_fn -> ioq_submit cycle. The cycle terminates naturally
++ * when io_submit(2) returns asynchronously (O_DIRECT), but can grow
++ * without bound when submissions complete synchronously. On overflow
++ * the caller returns without submitting; the outermost
++ * qemu_laio_process_completions() has already scheduled s->completion_bh
++ * (via qemu_bh_schedule() at the top of that function), which resumes
++ * submission from the next event-loop dispatch.
++ */
++#define IOQ_SUBMIT_MAX_DEPTH 8
++
+ struct qemu_laiocb {
+ Coroutine *co;
+ LinuxAioState *ctx;
+@@ -61,6 +74,7 @@ typedef struct {
+ unsigned int in_queue;
+ unsigned int in_flight;
+ bool blocked;
++ unsigned int submit_depth;
+ QSIMPLEQ_HEAD(, qemu_laiocb) pending;
+ } LaioQueue;
+
+@@ -331,6 +345,7 @@ static void ioq_init(LaioQueue *io_q)
+ io_q->in_queue = 0;
+ io_q->in_flight = 0;
+ io_q->blocked = false;
++ io_q->submit_depth = 0;
+ }
+
+ static void ioq_submit(LinuxAioState *s)
+@@ -340,6 +355,11 @@ static void ioq_submit(LinuxAioState *s)
+ QEMU_UNINITIALIZED struct iocb *iocbs[MAX_EVENTS];
+ QSIMPLEQ_HEAD(, qemu_laiocb) completed;
+
++ if (s->io_q.submit_depth >= IOQ_SUBMIT_MAX_DEPTH) {
++ return;
++ }
++ s->io_q.submit_depth++;
++
+ do {
+ if (s->io_q.in_flight >= MAX_EVENTS) {
+ break;
+@@ -385,6 +405,8 @@ static void ioq_submit(LinuxAioState *s)
+ * pended requests will be submitted from there.
+ */
+ }
++
++ s->io_q.submit_depth--;
+ }
+
+ static uint64_t laio_max_batch(LinuxAioState *s, uint64_t dev_max_batch)
+--
+2.52.0
+
diff --git a/kvm-block-mirror-check-range-when-setting-zero-bitmap-fo.patch b/kvm-block-mirror-check-range-when-setting-zero-bitmap-fo.patch
new file mode 100644
index 0000000..6663d1b
--- /dev/null
+++ b/kvm-block-mirror-check-range-when-setting-zero-bitmap-fo.patch
@@ -0,0 +1,74 @@
+From 86b2caec2f830d34a03f4f55028a525bcf11d515 Mon Sep 17 00:00:00 2001
+From: Fiona Ebner <f.ebner@proxmox.com>
+Date: Mon, 12 Jan 2026 16:23:51 +0100
+Subject: [PATCH 44/52] block/mirror: check range when setting zero bitmap for
+ sync write
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
+RH-Jira: RHEL-186384
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
+RH-Commit: [37/45] 858e42b5e327560c28e10a151d4e46a9cf273363 (kmwolf/centos-qemu-kvm)
+
+Some Proxmox users reported an occasional assertion failure [0][1] in
+busy VMs when using drive mirror with active mode. In particular, the
+failure may occur for zero writes shorter than the job granularity:
+
+> #0 0x00007b421154b507 in abort ()
+> #1 0x00007b421154b420 in ?? ()
+> #2 0x0000641c582e061f in bitmap_set (map=0x7b4204014e00, start=14, nr=-1)
+> #3 0x0000641c58062824 in do_sync_target_write (job=0x641c7e73d1e0,
+> method=MIRROR_METHOD_ZERO, offset=852480, bytes=4096, qiov=0x0, flags=0)
+> #4 0x0000641c58062250 in bdrv_mirror_top_do_write (bs=0x641c7e62e1f0,
+ method=MIRROR_METHOD_ZERO, copy_to_target=true, offset=852480,
+ bytes=4096, qiov=0x0, flags=0)
+> #5 0x0000641c58061f31 in bdrv_mirror_top_pwrite_zeroes (bs=0x641c7e62e1f0,
+ offset=852480, bytes=4096, flags=0)
+
+The range for the dirty bitmap described by dirty_bitmap_offset and
+dirty_bitmap_end is narrower than the original range and in fact,
+dirty_bitmap_end might be smaller than dirty_bitmap_offset. There
+already is a check for 'dirty_bitmap_offset < dirty_bitmap_end' before
+resetting the dirty bitmap. Add such a check for setting the zero
+bitmap too, which uses the same narrower range.
+
+[0]: https://forum.proxmox.com/threads/177981/
+[1]: https://bugzilla.proxmox.com/show_bug.cgi?id=7222
+
+Cc: qemu-stable@nongnu.org
+Fixes: 7e277545b9 ("mirror: Skip writing zeroes when target is already zero")
+Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
+Message-ID: <20260112152544.261923-1-f.ebner@proxmox.com>
+Reviewed-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
+Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
+(cherry picked from commit 4a7b1bd18d2e1a6b3796e177ae5df9b198264a0b)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ block/mirror.c | 9 ++++++---
+ 1 file changed, 6 insertions(+), 3 deletions(-)
+
+diff --git a/block/mirror.c b/block/mirror.c
+index c87f1e205b..d22f168ff0 100644
+--- a/block/mirror.c
++++ b/block/mirror.c
+@@ -1515,9 +1515,12 @@ do_sync_target_write(MirrorBlockJob *job, MirrorMethod method,
+ assert(!qiov);
+ ret = blk_co_pwrite_zeroes(job->target, offset, bytes, flags);
+ if (job->zero_bitmap && ret >= 0) {
+- bitmap_set(job->zero_bitmap, dirty_bitmap_offset / job->granularity,
+- (dirty_bitmap_end - dirty_bitmap_offset) /
+- job->granularity);
++ if (dirty_bitmap_offset < dirty_bitmap_end) {
++ bitmap_set(job->zero_bitmap,
++ dirty_bitmap_offset / job->granularity,
++ (dirty_bitmap_end - dirty_bitmap_offset) /
++ job->granularity);
++ }
+ }
+ break;
+
+--
+2.52.0
+
diff --git a/kvm-block-mirror-fix-assertion-failure-upon-duplicate-co.patch b/kvm-block-mirror-fix-assertion-failure-upon-duplicate-co.patch
new file mode 100644
index 0000000..ab63b65
--- /dev/null
+++ b/kvm-block-mirror-fix-assertion-failure-upon-duplicate-co.patch
@@ -0,0 +1,75 @@
+From 65523fcaa292ec714b66fa2b91ce8f50cd27ab64 Mon Sep 17 00:00:00 2001
+From: Fiona Ebner <f.ebner@proxmox.com>
+Date: Wed, 11 Mar 2026 15:54:25 +0100
+Subject: [PATCH 46/52] block/mirror: fix assertion failure upon duplicate
+ complete for job using 'replaces'
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
+RH-Jira: RHEL-186384
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
+RH-Commit: [39/45] 31abb5c45002235fdabe5cebfb7bee05e1b3c653 (kmwolf/centos-qemu-kvm)
+
+If s->replace_blocker was already set by an earlier invocation of
+mirror_complete(), then there will be an assertion failure when
+error_setg() is called for it a second time. The bdrv_op_block_all()
+and bdrv_ref() operations should only be done a single time too.
+
+Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
+Message-Id: <20260311145717.668492-2-f.ebner@proxmox.com>
+Reviewed-by: Hanna Czenczek <hreitz@redhat.com>
+Signed-off-by: Hanna Czenczek <hreitz@redhat.com>
+(cherry picked from commit 9ac85f4cc7995217db8f736733b990d6addcb036)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ block/mirror.c | 28 +++++++++++++++-------------
+ 1 file changed, 15 insertions(+), 13 deletions(-)
+
+diff --git a/block/mirror.c b/block/mirror.c
+index d22f168ff0..089856f4a8 100644
+--- a/block/mirror.c
++++ b/block/mirror.c
+@@ -1276,23 +1276,25 @@ static void mirror_complete(Job *job, Error **errp)
+ return;
+ }
+
+- /* block all operations on to_replace bs */
+- if (s->replaces) {
+- s->to_replace = bdrv_find_node(s->replaces);
+- if (!s->to_replace) {
+- error_setg(errp, "Node name '%s' not found", s->replaces);
+- return;
++ if (!s->should_complete) {
++ /* block all operations on to_replace bs */
++ if (s->replaces) {
++ s->to_replace = bdrv_find_node(s->replaces);
++ if (!s->to_replace) {
++ error_setg(errp, "Node name '%s' not found", s->replaces);
++ return;
++ }
++
++ /* TODO Translate this into child freeze system. */
++ error_setg(&s->replace_blocker,
++ "block device is in use by block-job-complete");
++ bdrv_op_block_all(s->to_replace, s->replace_blocker);
++ bdrv_ref(s->to_replace);
+ }
+
+- /* TODO Translate this into child freeze system. */
+- error_setg(&s->replace_blocker,
+- "block device is in use by block-job-complete");
+- bdrv_op_block_all(s->to_replace, s->replace_blocker);
+- bdrv_ref(s->to_replace);
++ s->should_complete = true;
+ }
+
+- s->should_complete = true;
+-
+ /* If the job is paused, it will be re-entered when it is resumed */
+ WITH_JOB_LOCK_GUARD() {
+ if (!job->paused) {
+--
+2.52.0
+
diff --git a/kvm-block-remove-detached-header-option-from-opts-after-.patch b/kvm-block-remove-detached-header-option-from-opts-after-.patch
new file mode 100644
index 0000000..b8c449b
--- /dev/null
+++ b/kvm-block-remove-detached-header-option-from-opts-after-.patch
@@ -0,0 +1,72 @@
+From 3984b109d0b4e1a86c46d3b79641b08043e5d64a Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= <berrange@redhat.com>
+Date: Fri, 19 Sep 2025 11:38:10 +0100
+Subject: [PATCH 16/52] block: remove 'detached-header' option from opts after
+ use
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
+RH-Jira: RHEL-186384
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
+RH-Commit: [9/45] 383bd840869b184dfe4588f930e2d924f6faec39 (kmwolf/centos-qemu-kvm)
+
+The code for creating LUKS devices references a 'detached-header'
+option in the QemuOpts data, but does not consume (remove) the
+option.
+
+Thus when the code later tries to convert the remaining unused
+QemuOpts into a QCryptoBlockCreateOptions struct, an error is
+reported by the QAPI code that 'detached-header' is not a valid
+field.
+
+This fixes a regression caused by
+
+ commit e818c01ae6e7c54c7019baaf307be59d99ce80b9
+ Author: Daniel P. Berrangé <berrange@redhat.com>
+ Date: Mon Feb 19 15:12:59 2024 +0000
+
+ qapi: drop unused QCryptoBlockCreateOptionsLUKS.detached-header
+
+which identified that the QAPI field was unused, but failed to
+realize the QemuOpts -> QCryptoBlockCreateOptions conversion
+was seeing the left-over 'detached-header' option which had not
+been removed from QemuOpts.
+
+This problem was identified by the 'luks-detached-header' I/O
+test, but unfortunately I/O tests are not run regularly for the
+LUKS format.
+
+Fixes: e818c01ae6e7c54c7019baaf307be59d99ce80b9
+Reported-by: Thomas Huth <thuth@redhat.com>
+Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
+Message-ID: <20250919103810.1513109-1-berrange@redhat.com>
+Reviewed-by: Eric Blake <eblake@redhat.com>
+Reviewed-by: Kevin Wolf <kwolf@redhat.com>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+(cherry picked from commit 6eda39a87f4fda78befa4085e3644e4440afc1dd)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ block/crypto.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/block/crypto.c b/block/crypto.c
+index d4226cc68a..17b4749a1e 100644
+--- a/block/crypto.c
++++ b/block/crypto.c
+@@ -792,7 +792,7 @@ block_crypto_co_create_opts_luks(BlockDriver *drv, const char *filename,
+ char *buf = NULL;
+ int64_t size;
+ bool detached_hdr =
+- qemu_opt_get_bool(opts, "detached-header", false);
++ qemu_opt_get_bool_del(opts, "detached-header", false);
+ unsigned int cflags = 0;
+ int ret;
+ Error *local_err = NULL;
+--
+2.52.0
+
diff --git a/kvm-block-throttle-groups-fix-deadlock-with-iolimits-and.patch b/kvm-block-throttle-groups-fix-deadlock-with-iolimits-and.patch
new file mode 100644
index 0000000..db0a441
--- /dev/null
+++ b/kvm-block-throttle-groups-fix-deadlock-with-iolimits-and.patch
@@ -0,0 +1,146 @@
+From 1c6a0b9352bf6eda335bd661869a46bc56d0c2fc Mon Sep 17 00:00:00 2001
+From: Dmitry Guryanov <dmitry.guryanov@gmail.com>
+Date: Mon, 8 Dec 2025 11:55:28 +0300
+Subject: [PATCH 20/52] block/throttle-groups: fix deadlock with iolimits and
+ muliple iothreads
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
+RH-Jira: RHEL-186384
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
+RH-Commit: [13/45] b36053836c5ced989f7b2495dbe170a36ad190c5 (kmwolf/centos-qemu-kvm)
+
+Details: https://gitlab.com/qemu-project/qemu/-/issues/3144
+
+The function schedule_next_request is called with tg->lock held and
+it may call throttle_group_co_restart_queue, which takes
+tgm->throttled_reqs_lock, qemu_co_mutex_lock may leave current
+coroutine if other iothread has taken the lock. If the next
+coroutine will call throttle_group_co_io_limits_intercept - it
+will try to take the mutex tg->lock which will never be released.
+
+Here is the backtrace of the iothread:
+Thread 30 (Thread 0x7f8aad1fd6c0 (LWP 24240) "IO iothread2"):
+ #0 futex_wait (futex_word=0x5611adb7d828, expected=2, private=0) at ../sysdeps/nptl/futex-internal.h:146
+ #1 __GI___lll_lock_wait (futex=futex@entry=0x5611adb7d828, private=0) at lowlevellock.c:49
+ #2 0x00007f8ab5a97501 in lll_mutex_lock_optimized (mutex=0x5611adb7d828) at pthread_mutex_lock.c:48
+ #3 ___pthread_mutex_lock (mutex=0x5611adb7d828) at pthread_mutex_lock.c:93
+ #4 0x00005611823f5482 in qemu_mutex_lock_impl (mutex=0x5611adb7d828, file=0x56118289daca "../block/throttle-groups.c", line=372) at ../util/qemu-thread-posix.c:94
+ #5 0x00005611822b0b39 in throttle_group_co_io_limits_intercept (tgm=0x5611af1bb4d8, bytes=4096, direction=THROTTLE_READ) at ../block/throttle-groups.c:372
+ #6 0x00005611822473b1 in blk_co_do_preadv_part (blk=0x5611af1bb490, offset=15972311040, bytes=4096, qiov=0x7f8aa4000f98, qiov_offset=0, flags=BDRV_REQ_REGISTERED_BUF) at ../block/block-backend.c:1354
+ #7 0x0000561182247fa0 in blk_aio_read_entry (opaque=0x7f8aa4005910) at ../block/block-backend.c:1619
+ #8 0x000056118241952e in coroutine_trampoline (i0=-1543497424, i1=32650) at ../util/coroutine-ucontext.c:175
+ #9 0x00007f8ab5a56f70 in ?? () at ../sysdeps/unix/sysv/linux/x86_64/__start_context.S:66 from target:/lib64/libc.so.6
+ #10 0x00007f8aad1ef190 in ?? ()
+ #11 0x0000000000000000 in ?? ()
+
+The lock is taken in line 386:
+(gdb) p tg.lock
+$1 = {lock = {__data = {__lock = 2, __count = 0, __owner = 24240, __nusers = 1, __kind = 0, __spins = 0, __elision = 0, __list = {__prev = 0x0, __next = 0x0}},
+ __size = "\002\000\000\000\000\000\000\000\260^\000\000\001", '\000' <repeats 26 times>, __align = 2}, file = 0x56118289daca "../block/throttle-groups.c",
+ line = 386, initialized = true}
+
+The solution is to use tg->lock to protect both ThreadGroup fields and
+ThrottleGroupMember.throttled_reqs. It doesn't seem to be possible
+to use separate locks because we need to first manipulate ThrottleGroup
+fields, then schedule next coroutine using throttled_reqs and after than
+update token field from ThrottleGroup depending on the throttled_reqs
+state.
+
+Signed-off-by: Dmitry Guryanov <dmitry.guryanov@gmail.com>
+Message-ID: <20251208085528.890098-1-dmitry.guryanov@gmail.com>
+Reviewed-by: Hanna Czenczek <hreitz@redhat.com>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+(cherry picked from commit d4816177654d59e26ce212c436513f01842eb410)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ block/throttle-groups.c | 21 ++++++---------------
+ include/block/throttle-groups.h | 3 +--
+ 2 files changed, 7 insertions(+), 17 deletions(-)
+
+diff --git a/block/throttle-groups.c b/block/throttle-groups.c
+index 66fdce9a90..5329ff1fdb 100644
+--- a/block/throttle-groups.c
++++ b/block/throttle-groups.c
+@@ -295,19 +295,15 @@ static bool throttle_group_schedule_timer(ThrottleGroupMember *tgm,
+ /* Start the next pending I/O request for a ThrottleGroupMember. Return whether
+ * any request was actually pending.
+ *
++ * This assumes that tg->lock is held.
++ *
+ * @tgm: the current ThrottleGroupMember
+ * @direction: the ThrottleDirection
+ */
+ static bool coroutine_fn throttle_group_co_restart_queue(ThrottleGroupMember *tgm,
+ ThrottleDirection direction)
+ {
+- bool ret;
+-
+- qemu_co_mutex_lock(&tgm->throttled_reqs_lock);
+- ret = qemu_co_queue_next(&tgm->throttled_reqs[direction]);
+- qemu_co_mutex_unlock(&tgm->throttled_reqs_lock);
+-
+- return ret;
++ return qemu_co_queue_next(&tgm->throttled_reqs[direction]);
+ }
+
+ /* Look for the next pending I/O request and schedule it.
+@@ -378,12 +374,8 @@ void coroutine_fn throttle_group_co_io_limits_intercept(ThrottleGroupMember *tgm
+ /* Wait if there's a timer set or queued requests of this type */
+ if (must_wait || tgm->pending_reqs[direction]) {
+ tgm->pending_reqs[direction]++;
+- qemu_mutex_unlock(&tg->lock);
+- qemu_co_mutex_lock(&tgm->throttled_reqs_lock);
+ qemu_co_queue_wait(&tgm->throttled_reqs[direction],
+- &tgm->throttled_reqs_lock);
+- qemu_co_mutex_unlock(&tgm->throttled_reqs_lock);
+- qemu_mutex_lock(&tg->lock);
++ &tg->lock);
+ tgm->pending_reqs[direction]--;
+ }
+
+@@ -410,15 +402,15 @@ static void coroutine_fn throttle_group_restart_queue_entry(void *opaque)
+ ThrottleDirection direction = data->direction;
+ bool empty_queue;
+
++ qemu_mutex_lock(&tg->lock);
+ empty_queue = !throttle_group_co_restart_queue(tgm, direction);
+
+ /* If the request queue was empty then we have to take care of
+ * scheduling the next one */
+ if (empty_queue) {
+- qemu_mutex_lock(&tg->lock);
+ schedule_next_request(tgm, direction);
+- qemu_mutex_unlock(&tg->lock);
+ }
++ qemu_mutex_unlock(&tg->lock);
+
+ g_free(data);
+
+@@ -569,7 +561,6 @@ void throttle_group_register_tgm(ThrottleGroupMember *tgm,
+ read_timer_cb,
+ write_timer_cb,
+ tgm);
+- qemu_co_mutex_init(&tgm->throttled_reqs_lock);
+ }
+
+ /* Unregister a ThrottleGroupMember from its group, removing it from the list,
+diff --git a/include/block/throttle-groups.h b/include/block/throttle-groups.h
+index 2355e8d9de..7dfc81f7b5 100644
+--- a/include/block/throttle-groups.h
++++ b/include/block/throttle-groups.h
+@@ -35,8 +35,7 @@
+
+ typedef struct ThrottleGroupMember {
+ AioContext *aio_context;
+- /* throttled_reqs_lock protects the CoQueues for throttled requests. */
+- CoMutex throttled_reqs_lock;
++ /* Protected by ThrottleGroup.lock */
+ CoQueue throttled_reqs[THROTTLE_MAX];
+
+ /* Nonzero if the I/O limits are currently being ignored; generally
+--
+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..b121dfd
--- /dev/null
+++ b/kvm-block-use-pwrite_zeroes_alignment-when-writing-first.patch
@@ -0,0 +1,103 @@
+From ca326a462336a694d317d03793b5f741125633b7 Mon Sep 17 00:00:00 2001
+From: Stefan Hajnoczi <stefanha@redhat.com>
+Date: Tue, 7 Oct 2025 10:16:59 -0400
+Subject: [PATCH 35/52] block: use pwrite_zeroes_alignment when writing first
+ sector
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
+RH-Jira: RHEL-186384
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
+RH-Commit: [28/45] aff37a9c095009b900cbf2d02ca378e21ec365aa (kmwolf/centos-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 <jean-louis@dupond.be>
+Buglink: https://gitlab.com/qemu-project/qemu/-/issues/3127
+Reviewed-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
+Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
+Message-ID: <20251007141700.71891-3-stefanha@redhat.com>
+Tested-by: Fiona Ebner <f.ebner@proxmox.com>
+Reviewed-by: Fiona Ebner <f.ebner@proxmox.com>
+Reviewed-by: Kevin Wolf <kwolf@redhat.com>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+(cherry picked from commit d704a13d2c025779bc91d04e127427347ddcf3b3)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ 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-block-vmdk-fix-OOB-read-in-vmdk_read_extent.patch b/kvm-block-vmdk-fix-OOB-read-in-vmdk_read_extent.patch
new file mode 100644
index 0000000..76f36f1
--- /dev/null
+++ b/kvm-block-vmdk-fix-OOB-read-in-vmdk_read_extent.patch
@@ -0,0 +1,51 @@
+From 9aac9cce4de8006a02800a3a20caaf11e87fc767 Mon Sep 17 00:00:00 2001
+From: "Halil Oktay (oblivionsage)" <cookieandcream560@gmail.com>
+Date: Tue, 10 Feb 2026 13:33:25 +0100
+Subject: [PATCH 19/52] block/vmdk: fix OOB read in vmdk_read_extent()
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
+RH-Jira: RHEL-186384
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
+RH-Commit: [12/45] 6af02246586e3e7a9a71a23df28a7ffb55110ead (kmwolf/centos-qemu-kvm)
+
+Bounds check for marker.size doesn't account for the 12-byte marker
+header, allowing zlib to read past the allocated buffer.
+
+Move the check inside the has_marker block and subtract the marker size.
+
+Fixes: CVE-2026-2243
+Reported-by: Halil Oktay (oblivionsage) <cookieandcream560@gmail.com>
+Signed-off-by: Halil Oktay (oblivionsage) <cookieandcream560@gmail.com>
+Reviewed-by: Kevin Wolf <kwolf@redhat.com>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+(cherry picked from commit cfda94eddb6c9c49b66461c950b22845a46a75c9)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ block/vmdk.c | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/block/vmdk.c b/block/vmdk.c
+index 7b98debc2b..7176435fc5 100644
+--- a/block/vmdk.c
++++ b/block/vmdk.c
+@@ -1951,10 +1951,10 @@ vmdk_read_extent(VmdkExtent *extent, int64_t cluster_offset,
+ marker = (VmdkGrainMarker *)cluster_buf;
+ compressed_data = marker->data;
+ data_len = le32_to_cpu(marker->size);
+- }
+- if (!data_len || data_len > buf_bytes) {
+- ret = -EINVAL;
+- goto out;
++ if (!data_len || data_len > buf_bytes - sizeof(VmdkGrainMarker)) {
++ ret = -EINVAL;
++ goto out;
++ }
+ }
+ ret = uncompress(uncomp_buf, &buf_len, compressed_data, data_len);
+ if (ret != Z_OK) {
+--
+2.52.0
+
diff --git a/kvm-commit-Drain-nodes-across-all-of-bdrv_commit.patch b/kvm-commit-Drain-nodes-across-all-of-bdrv_commit.patch
new file mode 100644
index 0000000..8af4c58
--- /dev/null
+++ b/kvm-commit-Drain-nodes-across-all-of-bdrv_commit.patch
@@ -0,0 +1,85 @@
+From 43fe4a491f9843019d780bfc41db44e86602f184 Mon Sep 17 00:00:00 2001
+From: Kevin Wolf <kwolf@redhat.com>
+Date: Mon, 27 Apr 2026 19:05:17 +0200
+Subject: [PATCH 47/52] commit: Drain nodes across all of bdrv_commit()
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
+RH-Jira: RHEL-186384
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
+RH-Commit: [40/45] 29cfac9b1a72bf5d4b4497eb4096bc05d41e146e (kmwolf/centos-qemu-kvm)
+
+The whole implementation of bdrv_commit() is only correct if no new
+writes come in while it's running: It has only a single loop checking
+the allocation status for each block and finally calls bdrv_make_empty()
+without checking if that throws away any new changes.
+
+We already have to drain while taking the graph write lock. Just extend
+the drained section to all of bdrv_commit() to make sure that we don't
+get any inconsistencies.
+
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+Message-ID: <20260427170520.101242-2-kwolf@redhat.com>
+Reviewed-by: Denis V. Lunev <den@openvz.org>
+Tested-by: Denis V. Lunev <den@openvz.org>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+(cherry picked from commit f0d9ccd46cf8fc576ab7d514f10f766546cdbc14)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ block/commit.c | 10 ++++++++--
+ 1 file changed, 8 insertions(+), 2 deletions(-)
+
+diff --git a/block/commit.c b/block/commit.c
+index 0d9e1a16d7..c5e3ef03a2 100644
+--- a/block/commit.c
++++ b/block/commit.c
+@@ -518,6 +518,7 @@ int bdrv_commit(BlockDriverState *bs)
+ if (!drv)
+ return -ENOMEDIUM;
+
++ bdrv_drain_all_begin();
+ bdrv_graph_rdlock_main_loop();
+
+ backing_file_bs = bdrv_cow_bs(bs);
+@@ -549,6 +550,10 @@ int bdrv_commit(BlockDriverState *bs)
+ BLK_PERM_ALL);
+ backing = blk_new(ctx, BLK_PERM_WRITE | BLK_PERM_RESIZE, BLK_PERM_ALL);
+
++ /* We drained all nodes, but still make requests through BlockBackends */
++ blk_set_disable_request_queuing(src, true);
++ blk_set_disable_request_queuing(backing, true);
++
+ ret = blk_insert_bs(src, bs, &local_err);
+ if (ret < 0) {
+ error_report_err(local_err);
+@@ -565,7 +570,7 @@ int bdrv_commit(BlockDriverState *bs)
+
+ bdrv_graph_rdunlock_main_loop();
+
+- bdrv_graph_wrlock_drained();
++ bdrv_graph_wrlock();
+ bdrv_set_backing_hd(commit_top_bs, backing_file_bs, &error_abort);
+ bdrv_set_backing_hd(bs, commit_top_bs, &error_abort);
+ bdrv_graph_wrunlock();
+@@ -647,7 +652,7 @@ ro_cleanup:
+ blk_unref(backing);
+
+ bdrv_graph_rdunlock_main_loop();
+- bdrv_graph_wrlock_drained();
++ bdrv_graph_wrlock();
+ if (bdrv_cow_bs(bs) != backing_file_bs) {
+ bdrv_set_backing_hd(bs, backing_file_bs, &error_abort);
+ }
+@@ -663,6 +668,7 @@ ro_cleanup:
+
+ out:
+ bdrv_graph_rdunlock_main_loop();
++ bdrv_drain_all_end();
+
+ return ret;
+ }
+--
+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..08ddd32
--- /dev/null
+++ b/kvm-file-posix-populate-pwrite_zeroes_alignment.patch
@@ -0,0 +1,65 @@
+From 746244e9df305293f9518dcb1dd636a7f8def4ac Mon Sep 17 00:00:00 2001
+From: Stefan Hajnoczi <stefanha@redhat.com>
+Date: Tue, 7 Oct 2025 10:16:58 -0400
+Subject: [PATCH 34/52] file-posix: populate pwrite_zeroes_alignment
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
+RH-Jira: RHEL-186384
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
+RH-Commit: [27/45] 4e268f2334911f502cc5290a93732acdd9a8df3b (kmwolf/centos-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 <stefanha@redhat.com>
+Message-ID: <20251007141700.71891-2-stefanha@redhat.com>
+Reviewed-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
+Tested-by: Fiona Ebner <f.ebner@proxmox.com>
+Reviewed-by: Fiona Ebner <f.ebner@proxmox.com>
+Reviewed-by: Kevin Wolf <kwolf@redhat.com>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+(cherry picked from commit 98e788b91ad037193b1fb375561ef7e0fef3c2fd)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ 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-hw-scsi-avoid-deadlock-upon-TMF-request-cancelling-w.patch b/kvm-hw-scsi-avoid-deadlock-upon-TMF-request-cancelling-w.patch
new file mode 100644
index 0000000..9d3076d
--- /dev/null
+++ b/kvm-hw-scsi-avoid-deadlock-upon-TMF-request-cancelling-w.patch
@@ -0,0 +1,93 @@
+From 71535e86e4aa44d7a2eefe15c0aaba1bbc0c1a10 Mon Sep 17 00:00:00 2001
+From: Fiona Ebner <f.ebner@proxmox.com>
+Date: Fri, 17 Oct 2025 11:43:30 +0200
+Subject: [PATCH 39/52] hw/scsi: avoid deadlock upon TMF request cancelling
+ with VirtIO
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
+RH-Jira: RHEL-186384
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
+RH-Commit: [32/45] 16f2462f42bd9a0205f40426a94a87448aae2292 (kmwolf/centos-qemu-kvm)
+
+When scsi_req_dequeue() is reached via
+scsi_req_cancel_async()
+virtio_scsi_tmf_cancel_req()
+virtio_scsi_do_tmf_aio_context(),
+there is a deadlock when trying to acquire the SCSI device's requests
+lock, because it was already acquired in
+virtio_scsi_do_tmf_aio_context().
+
+In particular, the issue happens with a FreeBSD guest (13, 14, 15,
+maybe more), when it cancels SCSI requests, because of timeout.
+
+This is a regression caused by commit da6eebb33b ("virtio-scsi:
+perform TMFs in appropriate AioContexts") and the introduction of the
+requests_lock earlier.
+
+To fix the issue, only cancel the requests after releasing the
+requests_lock. For this, the SCSI device's requests are iterated while
+holding the requests_lock and the requests to be cancelled are
+collected in a list. Then, the collected requests are cancelled
+one by one while not holding the requests_lock. This is safe, because
+only requests from the current AioContext are collected and acted
+upon.
+
+Originally reported by Proxmox VE users:
+https://bugzilla.proxmox.com/show_bug.cgi?id=6810
+https://forum.proxmox.com/threads/173914/
+
+Fixes: da6eebb33b ("virtio-scsi: perform TMFs in appropriate AioContexts")
+Suggested-by: Stefan Hajnoczi <stefanha@redhat.com>
+Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
+Message-id: 20251017094518.328905-1-f.ebner@proxmox.com
+[Changed g_list_append() to g_list_prepend() to avoid traversing the
+list each time.
+--Stefan]
+Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
+(cherry picked from commit 6910f04aa646f63a0257f77201ad8ea15992b816)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ hw/scsi/virtio-scsi.c | 14 +++++++++++++-
+ 1 file changed, 13 insertions(+), 1 deletion(-)
+
+diff --git a/hw/scsi/virtio-scsi.c b/hw/scsi/virtio-scsi.c
+index 0ce4718407..a632feedcb 100644
+--- a/hw/scsi/virtio-scsi.c
++++ b/hw/scsi/virtio-scsi.c
+@@ -343,6 +343,7 @@ static void virtio_scsi_do_tmf_aio_context(void *opaque)
+ SCSIDevice *d = virtio_scsi_device_get(s, tmf->req.tmf.lun);
+ SCSIRequest *r;
+ bool match_tag;
++ g_autoptr(GList) reqs = NULL;
+
+ if (!d) {
+ tmf->resp.tmf.response = VIRTIO_SCSI_S_BAD_TARGET;
+@@ -378,10 +379,21 @@ static void virtio_scsi_do_tmf_aio_context(void *opaque)
+ if (match_tag && cmd_req->req.cmd.tag != tmf->req.tmf.tag) {
+ continue;
+ }
+- virtio_scsi_tmf_cancel_req(tmf, r);
++ /*
++ * Cannot cancel directly, because scsi_req_dequeue() would deadlock
++ * when attempting to acquire the request_lock a second time. Taking
++ * a reference here is paired with an unref after cancelling below.
++ */
++ scsi_req_ref(r);
++ reqs = g_list_prepend(reqs, r);
+ }
+ }
+
++ for (GList *elem = g_list_first(reqs); elem; elem = g_list_next(elem)) {
++ virtio_scsi_tmf_cancel_req(tmf, elem->data);
++ scsi_req_unref(elem->data);
++ }
++
+ /* Incremented by virtio_scsi_do_tmf() */
+ virtio_scsi_tmf_dec_remaining(tmf);
+
+--
+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..82fba34
--- /dev/null
+++ b/kvm-ide-Clean-up-ide_trim_co_entry-to-be-idiomatic-corou.patch
@@ -0,0 +1,163 @@
+From 377ac72f84cb83972c65a190cc5bc4fdf9bdab9b Mon Sep 17 00:00:00 2001
+From: Kevin Wolf <kwolf@redhat.com>
+Date: Tue, 21 Apr 2026 18:11:30 +0200
+Subject: [PATCH 05/52] ide: Clean up ide_trim_co_entry() to be idiomatic
+ coroutine code
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 503: ide: Fix deadlock between TRIM and drain
+RH-Jira: RHEL-121686
+RH-Acked-by: Hanna Czenczek <hreitz@redhat.com>
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Commit: [5/7] 8d798543f3052121940fe208e13c710734b7c88c (kmwolf/centos-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 <kwolf@redhat.com>
+Message-ID: <20260421161132.99878-6-kwolf@redhat.com>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+(cherry picked from commit c1c71a7e167fdabaa9827d00c0be3aeafebdd921)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ 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-Fix-potential-assertion-failure-on-VM-stop-for-P.patch b/kvm-ide-Fix-potential-assertion-failure-on-VM-stop-for-P.patch
new file mode 100644
index 0000000..ae7c295
--- /dev/null
+++ b/kvm-ide-Fix-potential-assertion-failure-on-VM-stop-for-P.patch
@@ -0,0 +1,57 @@
+From ba430cce181388723ec2d3174195e1d468ed18b2 Mon Sep 17 00:00:00 2001
+From: Kevin Wolf <kwolf@redhat.com>
+Date: Thu, 26 Mar 2026 17:51:24 +0100
+Subject: [PATCH 41/52] ide: Fix potential assertion failure on VM stop for PIO
+ read error
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
+RH-Jira: RHEL-186384
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
+RH-Commit: [34/45] 4a6db09841ffa4323e11a9e47e170c919d292b3e (kmwolf/centos-qemu-kvm)
+
+ide_sector_read() as well as its callers neglect to call ide_set_retry()
+before starting I/O. If the I/O fails, this means that the retry
+information is stale. In particular, ide_handle_rw_error() has an
+assertion that s->bus->retry_unit == s->unit, which can fail if either
+there was no previous request or it came from another device on the bus.
+If the assertion weren't there, a wrong request would be retried after
+resuming the VM.
+
+Fix this by adding a ide_set_retry() call to ide_sector_read().
+
+This affects only reads because ide_transfer_start() does call
+ide_set_retry(). For writes, the data transfer comes first and the I/O
+is only started when the data has been read into s->io_buffer, so by
+that time, ide_set_retry() has been called. For reads, however, the I/O
+comes first and only then the data is transferred to the guest, so the
+call in ide_transfer_start() is too late.
+
+Buglink: https://redhat.atlassian.net/browse/RHEL-153537
+Reported-by: Tingting Mao <timao@redhat.com>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+Message-ID: <20260326165124.138593-1-kwolf@redhat.com>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+(cherry picked from commit 59c1d31136688415e5d682a87942292dbb3caaeb)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ hw/ide/core.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/hw/ide/core.c b/hw/ide/core.c
+index 8b9f06547b..bf616b7703 100644
+--- a/hw/ide/core.c
++++ b/hw/ide/core.c
+@@ -789,6 +789,7 @@ static void ide_sector_read(IDEState *s)
+ s->error = 0; /* not needed by IDE spec, but needed by Windows */
+ sector_num = ide_get_sector(s);
+ n = s->nsector;
++ ide_set_retry(s);
+
+ if (n == 0) {
+ ide_transfer_stop(s);
+--
+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..e7296a5
--- /dev/null
+++ b/kvm-ide-Minimal-fix-for-deadlock-between-TRIM-and-drain.patch
@@ -0,0 +1,141 @@
+From b2cc73c49eeee7f25626c4fdbb9aeaf561ffcdfd Mon Sep 17 00:00:00 2001
+From: Kevin Wolf <kwolf@redhat.com>
+Date: Tue, 21 Apr 2026 18:11:29 +0200
+Subject: [PATCH 04/52] ide: Minimal fix for deadlock between TRIM and drain
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 503: ide: Fix deadlock between TRIM and drain
+RH-Jira: RHEL-121686
+RH-Acked-by: Hanna Czenczek <hreitz@redhat.com>
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Commit: [4/7] 4324446f87c1e7d56ca13b3740d10bf5ed64e8c6 (kmwolf/centos-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 <kwolf@redhat.com>
+Message-ID: <20260421161132.99878-5-kwolf@redhat.com>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+(cherry picked from commit 095c08a7ba68cabaa6e0ce7a8a0804a949542c4c)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ 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..18107b6
--- /dev/null
+++ b/kvm-ide-test-Factor-out-wait_dma_completion.patch
@@ -0,0 +1,91 @@
+From 03274f77317b494c8a5b29c0b3bf7112592d5d8a Mon Sep 17 00:00:00 2001
+From: Kevin Wolf <kwolf@redhat.com>
+Date: Tue, 21 Apr 2026 18:11:31 +0200
+Subject: [PATCH 06/52] ide-test: Factor out wait_dma_completion()
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 503: ide: Fix deadlock between TRIM and drain
+RH-Jira: RHEL-121686
+RH-Acked-by: Hanna Czenczek <hreitz@redhat.com>
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Commit: [6/7] 640daa938349847f3531cfc90e9c9d4d2b05fcc7 (kmwolf/centos-qemu-kvm)
+
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+Message-ID: <20260421161132.99878-7-kwolf@redhat.com>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+(cherry picked from commit 92854c9c7539bdbf4f9c1abb33dd3ba59ff91e58)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ 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..995ea60
--- /dev/null
+++ b/kvm-ide-test-Test-reset-during-TRIM.patch
@@ -0,0 +1,181 @@
+From 1d7ff7d45d25d64fc17087a7efadca12fb9ee4e9 Mon Sep 17 00:00:00 2001
+From: Kevin Wolf <kwolf@redhat.com>
+Date: Tue, 21 Apr 2026 18:11:32 +0200
+Subject: [PATCH 07/52] ide-test: Test reset during TRIM
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 503: ide: Fix deadlock between TRIM and drain
+RH-Jira: RHEL-121686
+RH-Acked-by: Hanna Czenczek <hreitz@redhat.com>
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Commit: [7/7] ce0ab448a920d91472da7c89080eeafa3d9cf830 (kmwolf/centos-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 <kwolf@redhat.com>
+Message-ID: <20260421161132.99878-8-kwolf@redhat.com>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+(cherry picked from commit 2fa24e9755994f76f08ea2452215eb50f26f4c21)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ 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-046-Test-that-discard-write_zeroes-wait-for-.patch b/kvm-iotests-046-Test-that-discard-write_zeroes-wait-for-.patch
new file mode 100644
index 0000000..61846cc
--- /dev/null
+++ b/kvm-iotests-046-Test-that-discard-write_zeroes-wait-for-.patch
@@ -0,0 +1,153 @@
+From 37596bc8c96fb8261c13fc4f78d0f95ea8ab1397 Mon Sep 17 00:00:00 2001
+From: Kevin Wolf <kwolf@redhat.com>
+Date: Sat, 13 Jun 2026 23:03:38 +0300
+Subject: [PATCH 50/52] iotests/046: Test that discard/write_zeroes wait for
+ dependencies
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
+RH-Jira: RHEL-186384
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
+RH-Commit: [43/45] 7548e248ef6195c061c197e13865a49ab4586678 (kmwolf/centos-qemu-kvm)
+
+This is a regression test for the bug fixed in the previous commit where
+discard and write_zeroes operations wouldn't consider their dependencies
+in s->cluster_allocs. Without the fix, this results in a corrupted
+image.
+
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+Message-ID: <20260427170520.101242-5-kwolf@redhat.com>
+Reviewed-by: Denis V. Lunev <den@openvz.org>
+Tested-by: Denis V. Lunev <den@openvz.org>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+(cherry picked from commit 389f5bcc744d3ddc127d550a57261aed9bbba1f3)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+
+Message-ID: <20260613200411.1808021-55-mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ tests/qemu-iotests/046 | 46 ++++++++++++++++++++++++++++++++++++++
+ tests/qemu-iotests/046.out | 36 +++++++++++++++++++++++++++++
+ 2 files changed, 82 insertions(+)
+
+diff --git a/tests/qemu-iotests/046 b/tests/qemu-iotests/046
+index 4c9ed4d26e..e03dd40147 100755
+--- a/tests/qemu-iotests/046
++++ b/tests/qemu-iotests/046
+@@ -184,6 +184,48 @@ aio_write -P 160 0x104000 0x18000
+ resume A
+ aio_flush
+ EOF
++
++# Create a pre-allocated zero cluster, then start a write on it and discard it
++# before the L2 update is made
++cat <<EOF
++write -P 181 0x120000 0x10000
++write -z 0x120000 0x10000
++
++break write_aio A
++aio_write -P 180 0x120000 0x10000
++wait_break A
++aio_discard 0x120000 0x10000
++resume A
++aio_flush
++EOF
++
++# Create a pre-allocated zero cluster, then start a write on it and a
++# concurrent zero write with MAY_UNMAP before the L2 update is made
++cat <<EOF
++write -P 181 0x130000 0x10000
++write -z 0x130000 0x10000
++
++break write_aio A
++aio_write -P 180 0x130000 0x10000
++wait_break A
++aio_write -z -u 0x130000 0x10000
++resume A
++aio_flush
++EOF
++
++# Create a pre-allocated zero cluster, then start a write on it and a
++# concurrent zero write without MAY_UNMAP before the L2 update is made
++cat <<EOF
++write -P 181 0x140000 0x10000
++write -z 0x140000 0x10000
++
++break write_aio A
++aio_write -P 180 0x140000 0x10000
++wait_break A
++aio_write -z 0x140000 0x10000
++resume A
++aio_flush
++EOF
+ }
+
+ overlay_io | $QEMU_IO blkdebug::"$TEST_IMG" | _filter_qemu_io |\
+@@ -264,6 +306,10 @@ verify_io()
+ # Undefined content for 0x10c000 0x8000
+ echo read -P 160 0x114000 0x8000
+ echo read -P 17 0x11c000 0x4000
++
++ echo read -P 0 0x120000 0x10000
++ echo read -P 0 0x130000 0x10000
++ echo read -P 0 0x140000 0x10000
+ }
+
+ verify_io | $QEMU_IO "$TEST_IMG" | _filter_qemu_io
+diff --git a/tests/qemu-iotests/046.out b/tests/qemu-iotests/046.out
+index b1a03f4041..6341df335c 100644
+--- a/tests/qemu-iotests/046.out
++++ b/tests/qemu-iotests/046.out
+@@ -139,6 +139,36 @@ wrote XXX/XXX bytes at offset XXX
+ XXX KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+ wrote XXX/XXX bytes at offset XXX
+ XXX KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
++wrote XXX/XXX bytes at offset XXX
++XXX KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
++wrote XXX/XXX bytes at offset XXX
++XXX KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
++blkdebug: Suspended request 'A'
++blkdebug: Resuming request 'A'
++wrote XXX/XXX bytes at offset XXX
++XXX KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
++discarded XXX/XXX bytes at offset XXX
++XXX KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
++wrote XXX/XXX bytes at offset XXX
++XXX KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
++wrote XXX/XXX bytes at offset XXX
++XXX KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
++blkdebug: Suspended request 'A'
++blkdebug: Resuming request 'A'
++wrote XXX/XXX bytes at offset XXX
++XXX KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
++wrote XXX/XXX bytes at offset XXX
++XXX KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
++wrote XXX/XXX bytes at offset XXX
++XXX KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
++wrote XXX/XXX bytes at offset XXX
++XXX KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
++blkdebug: Suspended request 'A'
++blkdebug: Resuming request 'A'
++wrote XXX/XXX bytes at offset XXX
++XXX KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
++wrote XXX/XXX bytes at offset XXX
++XXX KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+
+ == Verify image content ==
+ read 65536/65536 bytes at offset 0
+@@ -239,5 +269,11 @@ read 32768/32768 bytes at offset 1130496
+ 32 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+ read 16384/16384 bytes at offset 1163264
+ 16 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
++read 65536/65536 bytes at offset 1179648
++64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
++read 65536/65536 bytes at offset 1245184
++64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
++read 65536/65536 bytes at offset 1310720
++64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+ No errors were found on the image.
+ *** done
+--
+2.52.0
+
diff --git a/kvm-iotests-147-ensure-temporary-sockets-are-closed-befo.patch b/kvm-iotests-147-ensure-temporary-sockets-are-closed-befo.patch
new file mode 100644
index 0000000..167474c
--- /dev/null
+++ b/kvm-iotests-147-ensure-temporary-sockets-are-closed-befo.patch
@@ -0,0 +1,42 @@
+From a25d168305434f211fded8dc89c3b849dcf759e0 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= <berrange@redhat.com>
+Date: Tue, 15 Jul 2025 15:30:21 +0100
+Subject: [PATCH 27/52] iotests/147: ensure temporary sockets are closed before
+ exiting
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
+RH-Jira: RHEL-186384
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
+RH-Commit: [20/45] 80c29c667a333d72e60c403ad8c4e07bf42db189 (kmwolf/centos-qemu-kvm)
+
+This avoids the python resource leak detector from issuing warnings
+in the iotests.
+
+Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
+(cherry picked from commit d4d0ebfcc926c11d16320d0d5accf22e3441c115)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ tests/qemu-iotests/147 | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/tests/qemu-iotests/147 b/tests/qemu-iotests/147
+index 6d6f077a14..3e14bd389a 100755
+--- a/tests/qemu-iotests/147
++++ b/tests/qemu-iotests/147
+@@ -277,6 +277,7 @@ class BuiltinNBD(NBDBlockdevAddBase):
+ } }
+ self.client_test(filename, flatten_sock_addr(address), 'nbd-export')
+
++ sockfd.close()
+ self._server_down()
+
+
+--
+2.52.0
+
diff --git a/kvm-iotests-151-ensure-subprocesses-are-cleaned-up.patch b/kvm-iotests-151-ensure-subprocesses-are-cleaned-up.patch
new file mode 100644
index 0000000..58dc1b1
--- /dev/null
+++ b/kvm-iotests-151-ensure-subprocesses-are-cleaned-up.patch
@@ -0,0 +1,49 @@
+From 45efe35d9113cbd76d4652ee56a805cbde58f6b1 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= <berrange@redhat.com>
+Date: Tue, 15 Jul 2025 15:30:22 +0100
+Subject: [PATCH 28/52] iotests/151: ensure subprocesses are cleaned up
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
+RH-Jira: RHEL-186384
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
+RH-Commit: [21/45] 58ae050da1f3c7bfee87daad073dcc99b8eec51b (kmwolf/centos-qemu-kvm)
+
+The iotest 151 creates a bunch of subprocesses, with their stdout
+connected to a pipe but never reads any data from them and does
+not gurantee the processes are killed on cleanup.
+
+This triggers resource leak warnings from python when the
+subprocess.Popen object is garbage collected.
+
+Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
+(cherry picked from commit 2b2fb25c2aaf5b2e8172d845db39cc50a951a12e)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ tests/qemu-iotests/151 | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+diff --git a/tests/qemu-iotests/151 b/tests/qemu-iotests/151
+index f2ff9c5dac..06ee3585db 100755
+--- a/tests/qemu-iotests/151
++++ b/tests/qemu-iotests/151
+@@ -263,6 +263,11 @@ class TestThrottledWithNbdExportBase(iotests.QMPTestCase):
+ break
+ except subprocess.TimeoutExpired:
+ self.vm.qtest(f'clock_step {1 * 1000 * 1000 * 1000}')
++ try:
++ p.kill()
++ p.stdout.close()
++ except:
++ pass
+ except IndexError:
+ pass
+
+--
+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..8d38be2
--- /dev/null
+++ b/kvm-iotests-add-Linux-loop-device-image-creation-test.patch
@@ -0,0 +1,116 @@
+From 915f7779592a69d1243427ed8e5e95285b6daa4f Mon Sep 17 00:00:00 2001
+From: Stefan Hajnoczi <stefanha@redhat.com>
+Date: Tue, 7 Oct 2025 10:17:00 -0400
+Subject: [PATCH 36/52] iotests: add Linux loop device image creation test
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
+RH-Jira: RHEL-186384
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
+RH-Commit: [29/45] b46da09b4329cc3b3c3263f2ac62b3386035de8d (kmwolf/centos-qemu-kvm)
+
+This qemu-iotests test case is based on the reproducer that Jean-Louis
+Dupond <jean-louis@dupond.be> shared in
+https://gitlab.com/qemu-project/qemu/-/issues/3127.
+
+Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
+Message-ID: <20251007141700.71891-4-stefanha@redhat.com>
+Reviewed-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
+Tested-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
+Tested-by: Fiona Ebner <f.ebner@proxmox.com>
+Reviewed-by: Fiona Ebner <f.ebner@proxmox.com>
+Reviewed-by: Kevin Wolf <kwolf@redhat.com>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+(cherry picked from commit 59a1cf0cd31597d2f6e2c18dc400a1de8427d47d)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ 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-iotests-test-active-mirror-with-unaligned-small-writ.patch b/kvm-iotests-test-active-mirror-with-unaligned-small-writ.patch
new file mode 100644
index 0000000..d7f101f
--- /dev/null
+++ b/kvm-iotests-test-active-mirror-with-unaligned-small-writ.patch
@@ -0,0 +1,77 @@
+From 86d893766713ddb454a9e7bf9e3069a41cf77b1e Mon Sep 17 00:00:00 2001
+From: Fiona Ebner <f.ebner@proxmox.com>
+Date: Tue, 20 Jan 2026 12:38:57 +0100
+Subject: [PATCH 45/52] iotests: test active mirror with unaligned, small write
+ zeroes op
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
+RH-Jira: RHEL-186384
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
+RH-Commit: [38/45] 39a5ae389b5bd6ee71ee61fb45fb8456e7068511 (kmwolf/centos-qemu-kvm)
+
+This tests the scenario fixed by "block/mirror: check range
+when setting zero bitmap for sync write" [0].
+
+[0] https://lore.kernel.org/qemu-devel/20260112152544.261923-1-f.ebner@proxmox.com/
+
+Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
+Message-ID: <20260120113859.251743-1-f.ebner@proxmox.com>
+Reviewed-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
+Tested-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
+Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
+(cherry picked from commit 267d7ae99a1d3b5be9d3421db3bdf651cc18c7ab)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ tests/qemu-iotests/151 | 20 ++++++++++++++++++++
+ tests/qemu-iotests/151.out | 4 ++--
+ 2 files changed, 22 insertions(+), 2 deletions(-)
+
+diff --git a/tests/qemu-iotests/151 b/tests/qemu-iotests/151
+index 06ee3585db..9b9c815db5 100755
+--- a/tests/qemu-iotests/151
++++ b/tests/qemu-iotests/151
+@@ -191,6 +191,26 @@ class TestActiveMirror(iotests.QMPTestCase):
+
+ self.potential_writes_in_flight = False
+
++ def testUnalignedSmallerThanGranularityWriteZeroes(self):
++ # Fill the source image
++ self.vm.hmp_qemu_io('source', 'write -P 1 0 %i' % self.image_len);
++
++ # Start the block job
++ self.vm.cmd('blockdev-mirror',
++ job_id='mirror',
++ filter_node_name='mirror-node',
++ device='source-node',
++ target='target-node',
++ sync='full',
++ copy_mode='write-blocking')
++
++ # Wait for the READY event
++ self.wait_ready(drive='mirror')
++
++ for offset in range(6 * self.image_len // 8, 7 * self.image_len // 8, 1024 * 1024):
++ self.vm.hmp_qemu_io('source', 'aio_write -z %i 512' % (offset + 512))
++
++ self.complete_and_wait(drive='mirror', wait_ready=False)
+
+ class TestThrottledWithNbdExportBase(iotests.QMPTestCase):
+ image_len = 128 * 1024 * 1024 # MB
+diff --git a/tests/qemu-iotests/151.out b/tests/qemu-iotests/151.out
+index 3f8a935a08..2f7d3902f2 100644
+--- a/tests/qemu-iotests/151.out
++++ b/tests/qemu-iotests/151.out
+@@ -1,5 +1,5 @@
+-......
++.......
+ ----------------------------------------------------------------------
+-Ran 6 tests
++Ran 7 tests
+
+ OK
+--
+2.52.0
+
diff --git a/kvm-nvme-Kick-and-check-completions-in-BDS-context.patch b/kvm-nvme-Kick-and-check-completions-in-BDS-context.patch
new file mode 100644
index 0000000..d78f395
--- /dev/null
+++ b/kvm-nvme-Kick-and-check-completions-in-BDS-context.patch
@@ -0,0 +1,75 @@
+From c9239fed6d3b7f14b847f10d53fd7c057e08cafd Mon Sep 17 00:00:00 2001
+From: Hanna Czenczek <hreitz@redhat.com>
+Date: Mon, 10 Nov 2025 16:48:42 +0100
+Subject: [PATCH 14/52] nvme: Kick and check completions in BDS context
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
+RH-Jira: RHEL-186384
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
+RH-Commit: [7/45] 6f2f54a3568591087538e0cc2d423ca71f44d661 (kmwolf/centos-qemu-kvm)
+
+nvme_process_completion() must run in the main BDS context, so schedule
+a BH for requests that aren’t there.
+
+The context in which we kick does not matter, but let’s just keep kick
+and process_completion together for simplicity’s sake.
+
+(For what it’s worth, a quick fio bandwidth test indicates that on my
+test hardware, if anything, this may be a bit better than kicking
+immediately before scheduling a pure nvme_process_completion() BH. But
+I wouldn’t take more from those results than that it doesn’t really seem
+to matter either way.)
+
+Cc: qemu-stable@nongnu.org
+Signed-off-by: Hanna Czenczek <hreitz@redhat.com>
+Message-ID: <20251110154854.151484-8-hreitz@redhat.com>
+Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
+Reviewed-by: Kevin Wolf <kwolf@redhat.com>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+(cherry picked from commit 7a501bbd51941fb1867d78e6b0d1dc69e396b9e2)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ block/nvme.c | 14 +++++++++++++-
+ 1 file changed, 13 insertions(+), 1 deletion(-)
+
+diff --git a/block/nvme.c b/block/nvme.c
+index 8df53ee4ca..7ed5f570bc 100644
+--- a/block/nvme.c
++++ b/block/nvme.c
+@@ -481,7 +481,7 @@ static void nvme_trace_command(const NvmeCmd *cmd)
+ }
+ }
+
+-static void nvme_deferred_fn(void *opaque)
++static void nvme_kick_and_check_completions(void *opaque)
+ {
+ NVMeQueuePair *q = opaque;
+
+@@ -490,6 +490,18 @@ static void nvme_deferred_fn(void *opaque)
+ nvme_process_completion(q);
+ }
+
++static void nvme_deferred_fn(void *opaque)
++{
++ NVMeQueuePair *q = opaque;
++
++ if (qemu_get_current_aio_context() == q->s->aio_context) {
++ nvme_kick_and_check_completions(q);
++ } else {
++ aio_bh_schedule_oneshot(q->s->aio_context,
++ nvme_kick_and_check_completions, q);
++ }
++}
++
+ static void nvme_submit_command(NVMeQueuePair *q, NVMeRequest *req,
+ NvmeCmd *cmd, BlockCompletionFunc cb,
+ void *opaque)
+--
+2.52.0
+
diff --git a/kvm-nvme-Note-in-which-AioContext-some-functions-run.patch b/kvm-nvme-Note-in-which-AioContext-some-functions-run.patch
new file mode 100644
index 0000000..04467fe
--- /dev/null
+++ b/kvm-nvme-Note-in-which-AioContext-some-functions-run.patch
@@ -0,0 +1,273 @@
+From af936e3ce66c2b8ab98fe6a529cea73d86f78ee9 Mon Sep 17 00:00:00 2001
+From: Hanna Czenczek <hreitz@redhat.com>
+Date: Mon, 10 Nov 2025 16:48:44 +0100
+Subject: [PATCH 15/52] nvme: Note in which AioContext some functions run
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
+RH-Jira: RHEL-186384
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
+RH-Commit: [8/45] 1ad4b84c0f2def1565fb3de24dcb6b3a17a18fec (kmwolf/centos-qemu-kvm)
+
+Sprinkle comments throughout block/nvme.c noting for some functions
+(where it may not be obvious) that they require a certain AioContext, or
+in which AioContext they do happen to run (for callbacks, BHs, event
+notifiers).
+
+Suggested-by: Kevin Wolf <kwolf@redhat.com>
+Signed-off-by: Hanna Czenczek <hreitz@redhat.com>
+Message-ID: <20251110154854.151484-10-hreitz@redhat.com>
+Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
+Reviewed-by: Kevin Wolf <kwolf@redhat.com>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+(cherry picked from commit ac3520f599fedee05945ce06bb0f71820a7b2ffc)
+(Mjt: pick this comments-only, no-code-changes commit to 10.1.x
+ so the next change applies cleanly)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ block/nvme.c | 43 ++++++++++++++++++++++++++++++++++---------
+ 1 file changed, 34 insertions(+), 9 deletions(-)
+
+diff --git a/block/nvme.c b/block/nvme.c
+index 7ed5f570bc..c3d3b99d1f 100644
+--- a/block/nvme.c
++++ b/block/nvme.c
+@@ -65,6 +65,7 @@ typedef struct {
+ } NVMeQueue;
+
+ typedef struct {
++ /* Called from nvme_process_completion() in the BDS's main AioContext */
+ BlockCompletionFunc *cb;
+ void *opaque;
+ int cid;
+@@ -84,6 +85,7 @@ typedef struct {
+ uint8_t *prp_list_pages;
+
+ /* Fields protected by @lock */
++ /* Coroutines in this queue are woken in their own context */
+ CoQueue free_req_queue;
+ NVMeQueue sq, cq;
+ int cq_phase;
+@@ -92,7 +94,7 @@ typedef struct {
+ int need_kick;
+ int inflight;
+
+- /* Thread-safe, no lock necessary */
++ /* Thread-safe, no lock necessary; runs in the BDS's main context */
+ QEMUBH *completion_bh;
+ } NVMeQueuePair;
+
+@@ -206,11 +208,13 @@ static void nvme_free_queue_pair(NVMeQueuePair *q)
+ g_free(q);
+ }
+
++/* Runs in the BDS's main AioContext */
+ static void nvme_free_req_queue_cb(void *opaque)
+ {
+ NVMeQueuePair *q = opaque;
+
+ qemu_mutex_lock(&q->lock);
++ /* qemu_co_enter_next() wakes the coroutine in its own AioContext */
+ while (q->free_req_head != -1 &&
+ qemu_co_enter_next(&q->free_req_queue, &q->lock)) {
+ /* Retry waiting requests */
+@@ -281,7 +285,7 @@ fail:
+ return NULL;
+ }
+
+-/* With q->lock */
++/* With q->lock, must be run in the BDS's main AioContext */
+ static void nvme_kick(NVMeQueuePair *q)
+ {
+ BDRVNVMeState *s = q->s;
+@@ -308,7 +312,10 @@ static NVMeRequest *nvme_get_free_req_nofail_locked(NVMeQueuePair *q)
+ return req;
+ }
+
+-/* Return a free request element if any, otherwise return NULL. */
++/*
++ * Return a free request element if any, otherwise return NULL.
++ * May be run from any AioContext.
++ */
+ static NVMeRequest *nvme_get_free_req_nowait(NVMeQueuePair *q)
+ {
+ QEMU_LOCK_GUARD(&q->lock);
+@@ -321,6 +328,7 @@ static NVMeRequest *nvme_get_free_req_nowait(NVMeQueuePair *q)
+ /*
+ * Wait for a free request to become available if necessary, then
+ * return it.
++ * May be called in any AioContext.
+ */
+ static coroutine_fn NVMeRequest *nvme_get_free_req(NVMeQueuePair *q)
+ {
+@@ -328,20 +336,21 @@ static coroutine_fn NVMeRequest *nvme_get_free_req(NVMeQueuePair *q)
+
+ while (q->free_req_head == -1) {
+ trace_nvme_free_req_queue_wait(q->s, q->index);
++ /* nvme_free_req_queue_cb() wakes us in our own AioContext */
+ qemu_co_queue_wait(&q->free_req_queue, &q->lock);
+ }
+
+ return nvme_get_free_req_nofail_locked(q);
+ }
+
+-/* With q->lock */
++/* With q->lock, may be called in any AioContext */
+ static void nvme_put_free_req_locked(NVMeQueuePair *q, NVMeRequest *req)
+ {
+ req->free_req_next = q->free_req_head;
+ q->free_req_head = req - q->reqs;
+ }
+
+-/* With q->lock */
++/* With q->lock, may be called in any AioContext */
+ static void nvme_wake_free_req_locked(NVMeQueuePair *q)
+ {
+ if (!qemu_co_queue_empty(&q->free_req_queue)) {
+@@ -350,7 +359,7 @@ static void nvme_wake_free_req_locked(NVMeQueuePair *q)
+ }
+ }
+
+-/* Insert a request in the freelist and wake waiters */
++/* Insert a request in the freelist and wake waiters (from any AioContext) */
+ static void nvme_put_free_req_and_wake(NVMeQueuePair *q, NVMeRequest *req)
+ {
+ qemu_mutex_lock(&q->lock);
+@@ -381,7 +390,7 @@ static inline int nvme_translate_error(const NvmeCqe *c)
+ }
+ }
+
+-/* With q->lock */
++/* With q->lock, must be run in the BDS's main AioContext */
+ static bool nvme_process_completion(NVMeQueuePair *q)
+ {
+ BDRVNVMeState *s = q->s;
+@@ -451,6 +460,7 @@ static bool nvme_process_completion(NVMeQueuePair *q)
+ return progress;
+ }
+
++/* As q->completion_bh, runs in the BDS's main AioContext */
+ static void nvme_process_completion_bh(void *opaque)
+ {
+ NVMeQueuePair *q = opaque;
+@@ -481,6 +491,7 @@ static void nvme_trace_command(const NvmeCmd *cmd)
+ }
+ }
+
++/* Must be run in the BDS's main AioContext */
+ static void nvme_kick_and_check_completions(void *opaque)
+ {
+ NVMeQueuePair *q = opaque;
+@@ -490,6 +501,7 @@ static void nvme_kick_and_check_completions(void *opaque)
+ nvme_process_completion(q);
+ }
+
++/* Runs in nvme_submit_command()'s AioContext */
+ static void nvme_deferred_fn(void *opaque)
+ {
+ NVMeQueuePair *q = opaque;
+@@ -502,6 +514,7 @@ static void nvme_deferred_fn(void *opaque)
+ }
+ }
+
++/* May be run in any AioContext */
+ static void nvme_submit_command(NVMeQueuePair *q, NVMeRequest *req,
+ NvmeCmd *cmd, BlockCompletionFunc cb,
+ void *opaque)
+@@ -523,6 +536,7 @@ static void nvme_submit_command(NVMeQueuePair *q, NVMeRequest *req,
+ defer_call(nvme_deferred_fn, q);
+ }
+
++/* Put into NVMeRequest.cb, so runs in the BDS's main AioContext */
+ static void nvme_admin_cmd_sync_cb(void *opaque, int ret)
+ {
+ int *pret = opaque;
+@@ -530,6 +544,7 @@ static void nvme_admin_cmd_sync_cb(void *opaque, int ret)
+ aio_wait_kick();
+ }
+
++/* Must be run in the BDS's or qemu's main AioContext */
+ static int nvme_admin_cmd_sync(BlockDriverState *bs, NvmeCmd *cmd)
+ {
+ BDRVNVMeState *s = bs->opaque;
+@@ -638,6 +653,7 @@ out:
+ return ret;
+ }
+
++/* Must be run in the BDS's main AioContext */
+ static void nvme_poll_queue(NVMeQueuePair *q)
+ {
+ const size_t cqe_offset = q->cq.head * NVME_CQ_ENTRY_BYTES;
+@@ -660,6 +676,7 @@ static void nvme_poll_queue(NVMeQueuePair *q)
+ qemu_mutex_unlock(&q->lock);
+ }
+
++/* Must be run in the BDS's main AioContext */
+ static void nvme_poll_queues(BDRVNVMeState *s)
+ {
+ int i;
+@@ -669,6 +686,7 @@ static void nvme_poll_queues(BDRVNVMeState *s)
+ }
+ }
+
++/* Run as an event notifier in the BDS's main AioContext */
+ static void nvme_handle_event(EventNotifier *n)
+ {
+ BDRVNVMeState *s = container_of(n, BDRVNVMeState,
+@@ -722,6 +740,7 @@ out_error:
+ return false;
+ }
+
++/* Run as an event notifier in the BDS's main AioContext */
+ static bool nvme_poll_cb(void *opaque)
+ {
+ EventNotifier *e = opaque;
+@@ -745,6 +764,7 @@ static bool nvme_poll_cb(void *opaque)
+ return false;
+ }
+
++/* Run as an event notifier in the BDS's main AioContext */
+ static void nvme_poll_ready(EventNotifier *e)
+ {
+ BDRVNVMeState *s = container_of(e, BDRVNVMeState,
+@@ -1050,7 +1070,7 @@ static int nvme_probe_blocksizes(BlockDriverState *bs, BlockSizes *bsz)
+ return 0;
+ }
+
+-/* Called with s->dma_map_lock */
++/* Called with s->dma_map_lock, may be run in any AioContext */
+ static coroutine_fn int nvme_cmd_unmap_qiov(BlockDriverState *bs,
+ QEMUIOVector *qiov)
+ {
+@@ -1061,13 +1081,17 @@ static coroutine_fn int nvme_cmd_unmap_qiov(BlockDriverState *bs,
+ if (!s->dma_map_count && !qemu_co_queue_empty(&s->dma_flush_queue)) {
+ r = qemu_vfio_dma_reset_temporary(s->vfio);
+ if (!r) {
++ /*
++ * Queue access is protected by the dma_map_lock, and all
++ * coroutines are woken in their own AioContext
++ */
+ qemu_co_queue_restart_all(&s->dma_flush_queue);
+ }
+ }
+ return r;
+ }
+
+-/* Called with s->dma_map_lock */
++/* Called with s->dma_map_lock, may be run in any AioContext */
+ static coroutine_fn int nvme_cmd_map_qiov(BlockDriverState *bs, NvmeCmd *cmd,
+ NVMeRequest *req, QEMUIOVector *qiov)
+ {
+@@ -1186,6 +1210,7 @@ static void nvme_rw_cb_bh(void *opaque)
+ qemu_coroutine_enter(data->co);
+ }
+
++/* Put into NVMeRequest.cb, so runs in the BDS's main AioContext */
+ static void nvme_rw_cb(void *opaque, int ret)
+ {
+ NVMeCoData *data = opaque;
+--
+2.52.0
+
diff --git a/kvm-python-backport-Remove-deprecated-get_event_loop-cal.patch b/kvm-python-backport-Remove-deprecated-get_event_loop-cal.patch
new file mode 100644
index 0000000..926a99d
--- /dev/null
+++ b/kvm-python-backport-Remove-deprecated-get_event_loop-cal.patch
@@ -0,0 +1,104 @@
+From d8bdd0cbf122d1f01acaec8c9846b9cdbf925d3c Mon Sep 17 00:00:00 2001
+From: John Snow <jsnow@redhat.com>
+Date: Tue, 13 Aug 2024 09:35:30 -0400
+Subject: [PATCH 25/52] python: backport 'Remove deprecated get_event_loop
+ calls'
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
+RH-Jira: RHEL-186384
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
+RH-Commit: [18/45] 7f8d672738b92fa5935342eded7559bc16bef7ab (kmwolf/centos-qemu-kvm)
+
+This method was deprecated in 3.12 because it ordinarily should not be
+used from coroutines; if there is not a currently running event loop,
+this automatically creates a new event loop - which is usually not what
+you want from code that would ever run in the bottom half.
+
+In our case, we do want this behavior in two places:
+
+(1) The synchronous shim, for convenience: this allows fully sync
+programs to use QEMUMonitorProtocol() without needing to set up an event
+loop beforehand. This is intentional to fully box in the async
+complexities into the legacy sync shim.
+
+(2) The qmp_tui shell; instead of relying on asyncio.run to create and
+run an asyncio program, we need to be able to pass the current asyncio
+loop to urwid setup functions. For convenience, again, we create one if
+one is not present to simplify the creation of the TUI appliance.
+
+The remaining user of get_event_loop() was in fact one of the erroneous
+users that should not have been using this function: if there's no
+running event loop inside of a coroutine, you're in big trouble :)
+
+Signed-off-by: John Snow <jsnow@redhat.com>
+cherry picked from commit python-qemu-qmp@aa1ff9907603a3033296027e1bd021133df86ef1
+Signed-off-by: John Snow <jsnow@redhat.com>
+Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
+(cherry picked from commit 5d99044d09db0fa8c2b3294e301927118f9effc9)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ python/qemu/qmp/legacy.py | 9 ++++++++-
+ python/qemu/qmp/qmp_tui.py | 7 ++++++-
+ python/tests/protocol.py | 2 +-
+ 3 files changed, 15 insertions(+), 3 deletions(-)
+
+diff --git a/python/qemu/qmp/legacy.py b/python/qemu/qmp/legacy.py
+index 22a2b5616e..ea9b8032c3 100644
+--- a/python/qemu/qmp/legacy.py
++++ b/python/qemu/qmp/legacy.py
+@@ -86,7 +86,14 @@ def __init__(self,
+ "server argument should be False when passing a socket")
+
+ self._qmp = QMPClient(nickname)
+- self._aloop = asyncio.get_event_loop()
++
++ try:
++ self._aloop = asyncio.get_running_loop()
++ except RuntimeError:
++ # No running loop; since this is a sync shim likely to be
++ # used in fully sync programs, create one if neccessary.
++ self._aloop = asyncio.get_event_loop_policy().get_event_loop()
++
+ self._address = address
+ self._timeout: Optional[float] = None
+
+diff --git a/python/qemu/qmp/qmp_tui.py b/python/qemu/qmp/qmp_tui.py
+index 562be008d5..651f611316 100644
+--- a/python/qemu/qmp/qmp_tui.py
++++ b/python/qemu/qmp/qmp_tui.py
+@@ -377,7 +377,12 @@ def run(self, debug: bool = False) -> None:
+ screen = urwid.raw_display.Screen()
+ screen.set_terminal_properties(256)
+
+- self.aloop = asyncio.get_event_loop()
++ try:
++ self.aloop = asyncio.get_running_loop()
++ except RuntimeError:
++ # No running asyncio event loop. Create one if necessary.
++ self.aloop = asyncio.get_event_loop_policy().get_event_loop()
++
+ self.aloop.set_debug(debug)
+
+ # Gracefully handle SIGTERM and SIGINT signals
+diff --git a/python/tests/protocol.py b/python/tests/protocol.py
+index c254c77b17..e565802516 100644
+--- a/python/tests/protocol.py
++++ b/python/tests/protocol.py
+@@ -227,7 +227,7 @@ def async_test(async_test_method):
+ Decorator; adds SetUp and TearDown to async tests.
+ """
+ async def _wrapper(self, *args, **kwargs):
+- loop = asyncio.get_event_loop()
++ loop = asyncio.get_running_loop()
+ loop.set_debug(True)
+
+ await self._asyncSetUp()
+--
+2.52.0
+
diff --git a/kvm-python-backport-avoid-creating-additional-event-loop.patch b/kvm-python-backport-avoid-creating-additional-event-loop.patch
new file mode 100644
index 0000000..a2b0a52
--- /dev/null
+++ b/kvm-python-backport-avoid-creating-additional-event-loop.patch
@@ -0,0 +1,210 @@
+From 3739feb5280370c3439030e1f1fe0ec0d0eb2ccb Mon Sep 17 00:00:00 2001
+From: John Snow <jsnow@redhat.com>
+Date: Wed, 3 Sep 2025 01:06:30 -0400
+Subject: [PATCH 26/52] python: backport 'avoid creating additional event loops
+ per thread'
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
+RH-Jira: RHEL-186384
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
+RH-Commit: [19/45] 1f870d1d45d5158c428bac46820d5aa7621f810f (kmwolf/centos-qemu-kvm)
+
+This commit is two backports squashed into one to avoid regressions.
+
+python: *really* remove get_event_loop
+
+A prior commit, aa1ff990, switched away from using get_event_loop *by
+default*, but this is not good enough to avoid deprecation warnings as
+`asyncio.get_event_loop_policy().get_event_loop()` is *also*
+deprecated. Replace this mechanism with explicit calls to
+asyncio.get_new_loop() and revise the cleanup mechanisms in __del__ to
+match.
+
+python: avoid creating additional event loops per thread
+
+"Too hasty by far!", commit 21ce2ee4 attempted to avoid deprecated
+behavior altogether by calling new_event_loop() directly if there was no
+loop currently running, but this has the unfortunate side effect of
+potentially creating multiple event loops per thread if tests
+instantiate multiple QMP connections in a single thread. This behavior
+is apparently not well-defined and causes problems in some, but not all,
+combinations of Python interpreter version and platform environment.
+
+Partially revert to Daniel Berrange's original patch, which calls
+get_event_loop and simply suppresses the deprecation warning in
+Python<=3.13. This time, however, additionally register new loops
+created with new_event_loop() so that future calls to get_event_loop()
+will return the loop already created.
+
+Reported-by: Richard W.M. Jones <rjones@redhat.com>
+Reported-by: Daniel P. Berrangé <berrange@redhat.com>
+Signed-off-by: John Snow <jsnow@redhat.com>
+cherry picked from commit python-qemu-qmp@21ce2ee4f2df87efe84a27b9c5112487f4670622
+cherry picked from commit python-qemu-qmp@c08fb82b38212956ccffc03fc6d015c3979f42fe
+Signed-off-by: John Snow <jsnow@redhat.com>
+Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
+(cherry picked from commit 85f223e5b031eb8ab63fbca314a4fb296a3a2632)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ python/qemu/qmp/legacy.py | 46 +++++++++++++++++++++++---------------
+ python/qemu/qmp/qmp_tui.py | 10 ++-------
+ python/qemu/qmp/util.py | 27 ++++++++++++++++++++++
+ 3 files changed, 57 insertions(+), 26 deletions(-)
+
+diff --git a/python/qemu/qmp/legacy.py b/python/qemu/qmp/legacy.py
+index ea9b8032c3..c732212c04 100644
+--- a/python/qemu/qmp/legacy.py
++++ b/python/qemu/qmp/legacy.py
+@@ -38,6 +38,7 @@
+ from .error import QMPError
+ from .protocol import Runstate, SocketAddrT
+ from .qmp_client import QMPClient
++from .util import get_or_create_event_loop
+
+
+ #: QMPMessage is an entire QMP message of any kind.
+@@ -86,17 +87,13 @@ def __init__(self,
+ "server argument should be False when passing a socket")
+
+ self._qmp = QMPClient(nickname)
+-
+- try:
+- self._aloop = asyncio.get_running_loop()
+- except RuntimeError:
+- # No running loop; since this is a sync shim likely to be
+- # used in fully sync programs, create one if neccessary.
+- self._aloop = asyncio.get_event_loop_policy().get_event_loop()
+-
+ self._address = address
+ self._timeout: Optional[float] = None
+
++ # This is a sync shim intended for use in fully synchronous
++ # programs. Create and set an event loop if necessary.
++ self._aloop = get_or_create_event_loop()
++
+ if server:
+ assert not isinstance(self._address, socket.socket)
+ self._sync(self._qmp.start_server(self._address))
+@@ -310,17 +307,30 @@ def send_fd_scm(self, fd: int) -> None:
+ self._qmp.send_fd_scm(fd)
+
+ def __del__(self) -> None:
+- if self._qmp.runstate == Runstate.IDLE:
+- return
++ if self._qmp.runstate != Runstate.IDLE:
++ self._qmp.logger.warning(
++ "QEMUMonitorProtocol object garbage collected without a prior "
++ "call to close()"
++ )
+
+ if not self._aloop.is_running():
+- self.close()
+- else:
+- # Garbage collection ran while the event loop was running.
+- # Nothing we can do about it now, but if we don't raise our
+- # own error, the user will be treated to a lot of traceback
+- # they might not understand.
++ if self._qmp.runstate != Runstate.IDLE:
++ # If the user neglected to close the QMP session and we
++ # are not currently running in an asyncio context, we
++ # have the opportunity to close the QMP session. If we
++ # do not do this, the error messages presented over
++ # dangling async resources may not make any sense to the
++ # user.
++ self.close()
++
++ if self._qmp.runstate != Runstate.IDLE:
++ # If QMP is still not quiesced, it means that the garbage
++ # collector ran from a context within the event loop and we
++ # are simply too late to take any corrective action. Raise
++ # our own error to give meaningful feedback to the user in
++ # order to prevent pages of asyncio stacktrace jargon.
+ raise QMPError(
+- "QEMUMonitorProtocol.close()"
+- " was not called before object was garbage collected"
++ "QEMUMonitorProtocol.close() was not called before object was "
++ "garbage collected, and could not be closed due to GC running "
++ "in the event loop"
+ )
+diff --git a/python/qemu/qmp/qmp_tui.py b/python/qemu/qmp/qmp_tui.py
+index 651f611316..89b0f5e081 100644
+--- a/python/qemu/qmp/qmp_tui.py
++++ b/python/qemu/qmp/qmp_tui.py
+@@ -40,7 +40,7 @@
+ from .message import DeserializationError, Message, UnexpectedTypeError
+ from .protocol import ConnectError, Runstate
+ from .qmp_client import ExecInterruptedError, QMPClient
+-from .util import pretty_traceback
++from .util import get_or_create_event_loop, pretty_traceback
+
+
+ # The name of the signal that is used to update the history list
+@@ -376,13 +376,7 @@ def run(self, debug: bool = False) -> None:
+ """
+ screen = urwid.raw_display.Screen()
+ screen.set_terminal_properties(256)
+-
+- try:
+- self.aloop = asyncio.get_running_loop()
+- except RuntimeError:
+- # No running asyncio event loop. Create one if necessary.
+- self.aloop = asyncio.get_event_loop_policy().get_event_loop()
+-
++ self.aloop = get_or_create_event_loop()
+ self.aloop.set_debug(debug)
+
+ # Gracefully handle SIGTERM and SIGINT signals
+diff --git a/python/qemu/qmp/util.py b/python/qemu/qmp/util.py
+index 0b3e781373..47ec39a8b5 100644
+--- a/python/qemu/qmp/util.py
++++ b/python/qemu/qmp/util.py
+@@ -10,6 +10,7 @@
+ import sys
+ import traceback
+ from typing import TypeVar, cast
++import warnings
+
+
+ T = TypeVar('T')
+@@ -20,6 +21,32 @@
+ # --------------------------
+
+
++def get_or_create_event_loop() -> asyncio.AbstractEventLoop:
++ """
++ Return this thread's current event loop, or create a new one.
++
++ This function behaves similarly to asyncio.get_event_loop() in
++ Python<=3.13, where if there is no event loop currently associated
++ with the current context, it will create and register one. It should
++ generally not be used in any asyncio-native applications.
++ """
++ try:
++ with warnings.catch_warnings():
++ # Python <= 3.13 will trigger deprecation warnings if no
++ # event loop is set, but will create and set a new loop.
++ warnings.simplefilter("ignore")
++ loop = asyncio.get_event_loop()
++ except RuntimeError:
++ # Python 3.14+: No event loop set for this thread,
++ # create and set one.
++ loop = asyncio.new_event_loop()
++ # Set this loop as the current thread's loop, to be returned
++ # by calls to get_event_loop() in the future.
++ asyncio.set_event_loop(loop)
++
++ return loop
++
++
+ async def flush(writer: asyncio.StreamWriter) -> None:
+ """
+ Utility function to ensure a StreamWriter is *fully* drained.
+--
+2.52.0
+
diff --git a/kvm-python-backport-drop-Python3.6-workarounds.patch b/kvm-python-backport-drop-Python3.6-workarounds.patch
new file mode 100644
index 0000000..bba142e
--- /dev/null
+++ b/kvm-python-backport-drop-Python3.6-workarounds.patch
@@ -0,0 +1,300 @@
+From 5de86fbbb5f6c0353bd2668c0424da28bf786264 Mon Sep 17 00:00:00 2001
+From: John Snow <jsnow@redhat.com>
+Date: Tue, 6 Jun 2023 13:19:11 -0400
+Subject: [PATCH 24/52] python: backport 'drop Python3.6 workarounds'
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
+RH-Jira: RHEL-186384
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
+RH-Commit: [17/45] 65ffd438927850c395611deb3821d01b71d3c2c1 (kmwolf/centos-qemu-kvm)
+
+Now that the minimum version is 3.7, drop some of the 3.6-specific hacks
+we've been carrying. A single remaining compatibility hack concerning
+3.6's lack of @asynccontextmanager is addressed in the following commit.
+
+Signed-off-by: John Snow <jsnow@redhat.com>
+cherry picked from commit python-qemu-qmp@3e8e34e594cfc6b707e6f67959166acde4b421b8
+Signed-off-by: John Snow <jsnow@redhat.com>
+Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
+(cherry picked from commit f9d2e0a3bd7ba2a693a892881f91cf53fa90cc71)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ python/qemu/qmp/protocol.py | 13 ++---
+ python/qemu/qmp/qmp_tui.py | 8 +--
+ python/qemu/qmp/util.py | 107 ++----------------------------------
+ python/tests/protocol.py | 8 +--
+ 4 files changed, 17 insertions(+), 119 deletions(-)
+
+diff --git a/python/qemu/qmp/protocol.py b/python/qemu/qmp/protocol.py
+index a4ffdfad51..4aff0ea423 100644
+--- a/python/qemu/qmp/protocol.py
++++ b/python/qemu/qmp/protocol.py
+@@ -36,13 +36,10 @@
+ from .error import QMPError
+ from .util import (
+ bottom_half,
+- create_task,
+ exception_summary,
+ flush,
+- is_closing,
+ pretty_traceback,
+ upper_half,
+- wait_closed,
+ )
+
+
+@@ -663,8 +660,8 @@ async def _establish_session(self) -> None:
+ reader_coro = self._bh_loop_forever(self._bh_recv_message, 'Reader')
+ writer_coro = self._bh_loop_forever(self._bh_send_message, 'Writer')
+
+- self._reader_task = create_task(reader_coro)
+- self._writer_task = create_task(writer_coro)
++ self._reader_task = asyncio.create_task(reader_coro)
++ self._writer_task = asyncio.create_task(writer_coro)
+
+ self._bh_tasks = asyncio.gather(
+ self._reader_task,
+@@ -689,7 +686,7 @@ def _schedule_disconnect(self) -> None:
+ if not self._dc_task:
+ self._set_state(Runstate.DISCONNECTING)
+ self.logger.debug("Scheduling disconnect.")
+- self._dc_task = create_task(self._bh_disconnect())
++ self._dc_task = asyncio.create_task(self._bh_disconnect())
+
+ @upper_half
+ async def _wait_disconnect(self) -> None:
+@@ -825,13 +822,13 @@ async def _bh_close_stream(self, error_pathway: bool = False) -> None:
+ if not self._writer:
+ return
+
+- if not is_closing(self._writer):
++ if not self._writer.is_closing():
+ self.logger.debug("Closing StreamWriter.")
+ self._writer.close()
+
+ self.logger.debug("Waiting for StreamWriter to close ...")
+ try:
+- await wait_closed(self._writer)
++ await self._writer.wait_closed()
+ except Exception: # pylint: disable=broad-except
+ # It's hard to tell if the Stream is already closed or
+ # not. Even if one of the tasks has failed, it may have
+diff --git a/python/qemu/qmp/qmp_tui.py b/python/qemu/qmp/qmp_tui.py
+index 2d9ebbd20b..562be008d5 100644
+--- a/python/qemu/qmp/qmp_tui.py
++++ b/python/qemu/qmp/qmp_tui.py
+@@ -40,7 +40,7 @@
+ from .message import DeserializationError, Message, UnexpectedTypeError
+ from .protocol import ConnectError, Runstate
+ from .qmp_client import ExecInterruptedError, QMPClient
+-from .util import create_task, pretty_traceback
++from .util import pretty_traceback
+
+
+ # The name of the signal that is used to update the history list
+@@ -225,7 +225,7 @@ def cb_send_to_server(self, raw_msg: str) -> None:
+ """
+ try:
+ msg = Message(bytes(raw_msg, encoding='utf-8'))
+- create_task(self._send_to_server(msg))
++ asyncio.create_task(self._send_to_server(msg))
+ except (DeserializationError, UnexpectedTypeError) as err:
+ raw_msg = format_json(raw_msg)
+ logging.info('Invalid message: %s', err.error_message)
+@@ -246,7 +246,7 @@ def kill_app(self) -> None:
+ Initiates killing of app. A bridge between asynchronous and synchronous
+ code.
+ """
+- create_task(self._kill_app())
++ asyncio.create_task(self._kill_app())
+
+ async def _kill_app(self) -> None:
+ """
+@@ -393,7 +393,7 @@ def run(self, debug: bool = False) -> None:
+ handle_mouse=True,
+ event_loop=event_loop)
+
+- create_task(self.manage_connection(), self.aloop)
++ self.aloop.create_task(self.manage_connection())
+ try:
+ main_loop.run()
+ except Exception as err:
+diff --git a/python/qemu/qmp/util.py b/python/qemu/qmp/util.py
+index ca6225e9cd..0b3e781373 100644
+--- a/python/qemu/qmp/util.py
++++ b/python/qemu/qmp/util.py
+@@ -1,25 +1,15 @@
+ """
+ Miscellaneous Utilities
+
+-This module provides asyncio utilities and compatibility wrappers for
+-Python 3.6 to provide some features that otherwise become available in
+-Python 3.7+.
+-
+-Various logging and debugging utilities are also provided, such as
+-`exception_summary()` and `pretty_traceback()`, used primarily for
+-adding information into the logging stream.
++This module provides asyncio and various logging and debugging
++utilities, such as `exception_summary()` and `pretty_traceback()`, used
++primarily for adding information into the logging stream.
+ """
+
+ import asyncio
+ import sys
+ import traceback
+-from typing import (
+- Any,
+- Coroutine,
+- Optional,
+- TypeVar,
+- cast,
+-)
++from typing import TypeVar, cast
+
+
+ T = TypeVar('T')
+@@ -79,95 +69,6 @@ def bottom_half(func: T) -> T:
+ return func
+
+
+-# -------------------------------
+-# Section: Compatibility Wrappers
+-# -------------------------------
+-
+-
+-def create_task(coro: Coroutine[Any, Any, T],
+- loop: Optional[asyncio.AbstractEventLoop] = None
+- ) -> 'asyncio.Future[T]':
+- """
+- Python 3.6-compatible `asyncio.create_task` wrapper.
+-
+- :param coro: The coroutine to execute in a task.
+- :param loop: Optionally, the loop to create the task in.
+-
+- :return: An `asyncio.Future` object.
+- """
+- if sys.version_info >= (3, 7):
+- if loop is not None:
+- return loop.create_task(coro)
+- return asyncio.create_task(coro) # pylint: disable=no-member
+-
+- # Python 3.6:
+- return asyncio.ensure_future(coro, loop=loop)
+-
+-
+-def is_closing(writer: asyncio.StreamWriter) -> bool:
+- """
+- Python 3.6-compatible `asyncio.StreamWriter.is_closing` wrapper.
+-
+- :param writer: The `asyncio.StreamWriter` object.
+- :return: `True` if the writer is closing, or closed.
+- """
+- if sys.version_info >= (3, 7):
+- return writer.is_closing()
+-
+- # Python 3.6:
+- transport = writer.transport
+- assert isinstance(transport, asyncio.WriteTransport)
+- return transport.is_closing()
+-
+-
+-async def wait_closed(writer: asyncio.StreamWriter) -> None:
+- """
+- Python 3.6-compatible `asyncio.StreamWriter.wait_closed` wrapper.
+-
+- :param writer: The `asyncio.StreamWriter` to wait on.
+- """
+- if sys.version_info >= (3, 7):
+- await writer.wait_closed()
+- return
+-
+- # Python 3.6
+- transport = writer.transport
+- assert isinstance(transport, asyncio.WriteTransport)
+-
+- while not transport.is_closing():
+- await asyncio.sleep(0)
+-
+- # This is an ugly workaround, but it's the best I can come up with.
+- sock = transport.get_extra_info('socket')
+-
+- if sock is None:
+- # Our transport doesn't have a socket? ...
+- # Nothing we can reasonably do.
+- return
+-
+- while sock.fileno() != -1:
+- await asyncio.sleep(0)
+-
+-
+-def asyncio_run(coro: Coroutine[Any, Any, T], *, debug: bool = False) -> T:
+- """
+- Python 3.6-compatible `asyncio.run` wrapper.
+-
+- :param coro: A coroutine to execute now.
+- :return: The return value from the coroutine.
+- """
+- if sys.version_info >= (3, 7):
+- return asyncio.run(coro, debug=debug)
+-
+- # Python 3.6
+- loop = asyncio.get_event_loop()
+- loop.set_debug(debug)
+- ret = loop.run_until_complete(coro)
+- loop.close()
+-
+- return ret
+-
+-
+ # ----------------------------
+ # Section: Logging & Debugging
+ # ----------------------------
+diff --git a/python/tests/protocol.py b/python/tests/protocol.py
+index 56c4d441f9..c254c77b17 100644
+--- a/python/tests/protocol.py
++++ b/python/tests/protocol.py
+@@ -8,7 +8,6 @@
+
+ from qemu.qmp import ConnectError, Runstate
+ from qemu.qmp.protocol import AsyncProtocol, StateError
+-from qemu.qmp.util import asyncio_run, create_task
+
+
+ class NullProtocol(AsyncProtocol[None]):
+@@ -124,7 +123,7 @@ async def _runner():
+ if allow_cancellation:
+ return
+ raise
+- return create_task(_runner())
++ return asyncio.create_task(_runner())
+
+
+ @contextmanager
+@@ -271,7 +270,7 @@ async def _watcher():
+ msg=f"Expected state '{state.name}'",
+ )
+
+- self.runstate_watcher = create_task(_watcher())
++ self.runstate_watcher = asyncio.create_task(_watcher())
+ # Kick the loop and force the task to block on the event.
+ await asyncio.sleep(0)
+
+@@ -589,7 +588,8 @@ async def _asyncTearDown(self):
+ async def testSmoke(self):
+ with TemporaryDirectory(suffix='.qmp') as tmpdir:
+ sock = os.path.join(tmpdir, type(self.proto).__name__ + ".sock")
+- server_task = create_task(self.server.start_server_and_accept(sock))
++ server_task = asyncio.create_task(
++ self.server.start_server_and_accept(sock))
+
+ # give the server a chance to start listening [...]
+ await asyncio.sleep(0)
+--
+2.52.0
+
diff --git a/kvm-qcow2-Fix-corruption-on-discard-during-write-with-CO.patch b/kvm-qcow2-Fix-corruption-on-discard-during-write-with-CO.patch
new file mode 100644
index 0000000..d62a04d
--- /dev/null
+++ b/kvm-qcow2-Fix-corruption-on-discard-during-write-with-CO.patch
@@ -0,0 +1,162 @@
+From 21a26a72ecde7527ece5fdef3ba23f10084fd43d Mon Sep 17 00:00:00 2001
+From: Kevin Wolf <kwolf@redhat.com>
+Date: Sat, 13 Jun 2026 23:03:37 +0300
+Subject: [PATCH 49/52] qcow2: Fix corruption on discard during write with COW
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
+RH-Jira: RHEL-186384
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
+RH-Commit: [42/45] 5569e7b55c4b1c45632b64057b774a4523c23ac1 (kmwolf/centos-qemu-kvm)
+
+Most code in qcow2 that accesses (and potentially modifies) L2 tables
+does so while holding s->lock.
+
+There is one exception, which is allocating writes. They hold the lock
+initially while allocating clusters, but drop it for writing the guest
+payload before taking the lock again for updating the L2 tables. This
+allows concurrent requests that touch other parts of the image file to
+continue in parallel and is an important performance optimisation.
+
+However, this means that other requests that run while the lock is
+dropped for writing guest data must synchronise with the list of
+allocating requests in s->cluster_allocs and wait if they would overlap.
+For writes, this is done in handle_dependencies(), but discard and write
+zeros operations neglect to synchronise with s->cluster_allocs.
+
+This means that discard can free a cluster whose L2 entry will already
+be modified in qcow2_alloc_cluster_link_l2() by a previously started
+write. In the case of a pre-allocated zero cluster that is in the
+process of being overwritten, this means that discard can lead to a
+situation where the cluster is still mapped (because the write will
+restore the L2 entry just without the zero flag), but its refcount has
+been decreased, resulting in a corrupted image.
+
+Add the missing synchronisation to qcow2_cluster_discard() and
+qcow2_subcluster_zeroize() to fix the problem.
+
+Cc: qemu-stable@nongnu.org
+Reported-by: Denis V. Lunev <den@openvz.org>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+Message-ID: <20260427170520.101242-4-kwolf@redhat.com>
+Reviewed-by: Denis V. Lunev <den@openvz.org>
+Tested-by: Denis V. Lunev <den@openvz.org>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+(cherry picked from commit b8bfb1478d61512f851badd0d912c6661a2efee7)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+
+Message-ID: <20260613200411.1808021-54-mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ block/qcow2-cluster.c | 52 ++++++++++++++++++++++++++++++++++++++++---
+ 1 file changed, 49 insertions(+), 3 deletions(-)
+
+diff --git a/block/qcow2-cluster.c b/block/qcow2-cluster.c
+index ce8c0076b3..c20011d34c 100644
+--- a/block/qcow2-cluster.c
++++ b/block/qcow2-cluster.c
+@@ -1392,6 +1392,9 @@ count_single_write_clusters(BlockDriverState *bs, int nb_clusters,
+ * the same cluster. In this case we need to wait until the previous
+ * request has completed and updated the L2 table accordingly.
+ *
++ * If allow_shortening == true, instead of waiting for a dependency, *cur_bytes
++ * can be shortened so that the cluster allocations don't overlap.
++ *
+ * Returns:
+ * 0 if there was no dependency. *cur_bytes indicates the number of
+ * bytes from guest_offset that can be read before the next
+@@ -1403,7 +1406,9 @@ count_single_write_clusters(BlockDriverState *bs, int nb_clusters,
+ */
+ static int coroutine_fn handle_dependencies(BlockDriverState *bs,
+ uint64_t guest_offset,
+- uint64_t *cur_bytes, QCowL2Meta **m)
++ uint64_t *cur_bytes,
++ bool allow_shortening,
++ QCowL2Meta **m)
+ {
+ BDRVQcow2State *s = bs->opaque;
+ QCowL2Meta *old_alloc;
+@@ -1434,7 +1439,7 @@ static int coroutine_fn handle_dependencies(BlockDriverState *bs,
+
+ /* Conflict */
+
+- if (start < old_start) {
++ if (start < old_start && allow_shortening) {
+ /* Stop at the start of a running allocation */
+ bytes = old_start - start;
+ } else {
+@@ -1469,6 +1474,29 @@ static int coroutine_fn handle_dependencies(BlockDriverState *bs,
+ return 0;
+ }
+
++static void coroutine_mixed_fn wait_for_dependencies(BlockDriverState *bs,
++ uint64_t guest_offset,
++ uint64_t bytes)
++{
++ BDRVQcow2State *s = bs->opaque;
++ QCowL2Meta *m = NULL;
++ int ret;
++
++ /*
++ * Discard has some non-coroutine callers (creating internal snapshots and
++ * make empty). They are calling from qemu-img or in a drained section, so
++ * we know that no writes can be in progress.
++ */
++ if (!qemu_in_coroutine()) {
++ assert(QLIST_EMPTY(&s->cluster_allocs));
++ return;
++ }
++
++ do {
++ ret = handle_dependencies(bs, guest_offset, &bytes, false, &m);
++ } while (ret == -EAGAIN);
++}
++
+ /*
+ * Checks how many already allocated clusters that don't require a new
+ * allocation there are at the given guest_offset (up to *bytes).
+@@ -1840,7 +1868,7 @@ again:
+ * the right synchronisation between the in-flight request and
+ * the new one.
+ */
+- ret = handle_dependencies(bs, start, &cur_bytes, m);
++ ret = handle_dependencies(bs, start, &cur_bytes, true, m);
+ if (ret == -EAGAIN) {
+ /* Currently handle_dependencies() doesn't yield if we already had
+ * an allocation. If it did, we would have to clean up the L2Meta
+@@ -2002,6 +2030,15 @@ int qcow2_cluster_discard(BlockDriverState *bs, uint64_t offset,
+ int64_t cleared;
+ int ret;
+
++ /*
++ * If we're touching a cluster for which allocating writes are in flight,
++ * wait for them to complete to avoid conflicting metadata updates.
++ *
++ * We don't need to allocate a QCowL2Meta for the discard operation because
++ * s->lock is held for the duration of the whole operation.
++ */
++ wait_for_dependencies(bs, offset, bytes);
++
+ /* Caller must pass aligned values, except at image end */
+ assert(QEMU_IS_ALIGNED(offset, s->cluster_size));
+ assert(QEMU_IS_ALIGNED(end_offset, s->cluster_size) ||
+@@ -2164,6 +2201,15 @@ int coroutine_fn qcow2_subcluster_zeroize(BlockDriverState *bs, uint64_t offset,
+ int64_t cleared;
+ int ret;
+
++ /*
++ * If we're touching a cluster for which allocating writes are in flight,
++ * wait for them to complete to avoid conflicting metadata updates.
++ *
++ * We don't need to allocate a QCowL2Meta for the zeroize operation because
++ * s->lock is held for the duration of the whole operation.
++ */
++ wait_for_dependencies(bs, offset, bytes);
++
+ /* If we have to stay in sync with an external data file, zero out
+ * s->data_file first. */
+ if (data_file_is_raw(bs)) {
+--
+2.52.0
+
diff --git a/kvm-qcow2-Fix-data-loss-on-zero-write-with-detect-zeroes.patch b/kvm-qcow2-Fix-data-loss-on-zero-write-with-detect-zeroes.patch
new file mode 100644
index 0000000..f94c11d
--- /dev/null
+++ b/kvm-qcow2-Fix-data-loss-on-zero-write-with-detect-zeroes.patch
@@ -0,0 +1,205 @@
+From aff7b26c2a2ad0c78e403a572a5cca52e1f2774e Mon Sep 17 00:00:00 2001
+From: Thomas Lamprecht <t.lamprecht@proxmox.com>
+Date: Sat, 13 Jun 2026 23:03:39 +0300
+Subject: [PATCH 51/52] qcow2: Fix data loss on zero write with
+ detect-zeroes=unmap
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
+RH-Jira: RHEL-186384
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
+RH-Commit: [44/45] 0d2cddb6cdde22f5842aeca19fcab691230f50c8 (kmwolf/centos-qemu-kvm)
+
+Commit b8bfb1478d ("qcow2: Fix corruption on discard during write with
+COW") added a wait_for_dependencies() at the start of
+qcow2_subcluster_zeroize(). That fixes the inconsistency it set out to
+fix, but turns the lock-protected pre-check in the caller,
+qcow2_co_pwrite_zeroes(), into a stale one: the wait yields s->lock,
+so an in-flight allocating write whose QCowL2Meta is already on
+s->cluster_allocs (but whose L2 entry is not yet linked) gets to link
+its entry during the yield. When the zeroize wakes, the cluster is now
+NORMAL, and with BDRV_REQ_MAY_UNMAP the free path in zero_in_l2_slice()
+unmaps the just-written cluster, silently dropping the data write's
+payload.
+
+This is reachable with detect-zeroes=unmap (the default for VirtIO
+disks with discard on in Proxmox VE), under which the block layer
+auto-promotes all-zero buffers to BDRV_REQ_ZERO_WRITE |
+BDRV_REQ_MAY_UNMAP. A memory-constrained Debian guest running 'apt
+full-upgrade' on such a disk reproduces it as random SIGSEGVs:
+swapped-out code pages come back as zero.
+
+Wait for in-flight dependencies before the lock-protected check in
+qcow2_co_pwrite_zeroes(). If a write linked its L2 entry during the
+wait, the type check now fails and the block layer falls back to a
+bounce-buffered zero write that only touches the requested subrange,
+preserving the racing write's data. Promote wait_for_dependencies() to
+qcow2_wait_for_dependencies() so qcow2.c can call it.
+
+Fixes: b8bfb1478d ("qcow2: Fix corruption on discard during write with COW")
+Fixes: d85e00dd03 ("qcow2: Fix corruption on discard during write with COW") in 10.2.x series
+Cc: qemu-stable@nongnu.org
+Tested-by: Fiona Ebner <f.ebner@proxmox.com>
+Reviewed-by: Fiona Ebner <f.ebner@proxmox.com>
+Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
+Message-ID: <20260522151318.238064-1-t.lamprecht@proxmox.com>
+[kwolf: Reverted unnecessary change to 'nr' assignment]
+Reviewed-by: Kevin Wolf <kwolf@redhat.com>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+(cherry picked from commit 1d47eb68983577a4e06fe1c165d90e128b191b86)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+
+Message-ID: <20260613200411.1808021-56-mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ block/qcow2-cluster.c | 10 +++++-----
+ block/qcow2.c | 8 +++++++-
+ block/qcow2.h | 4 ++++
+ tests/qemu-iotests/046 | 23 +++++++++++++++++++++++
+ tests/qemu-iotests/046.out | 10 ++++++++++
+ 5 files changed, 49 insertions(+), 6 deletions(-)
+
+diff --git a/block/qcow2-cluster.c b/block/qcow2-cluster.c
+index c20011d34c..23eeb9fc56 100644
+--- a/block/qcow2-cluster.c
++++ b/block/qcow2-cluster.c
+@@ -1474,9 +1474,9 @@ static int coroutine_fn handle_dependencies(BlockDriverState *bs,
+ return 0;
+ }
+
+-static void coroutine_mixed_fn wait_for_dependencies(BlockDriverState *bs,
+- uint64_t guest_offset,
+- uint64_t bytes)
++void coroutine_mixed_fn qcow2_wait_for_dependencies(BlockDriverState *bs,
++ uint64_t guest_offset,
++ uint64_t bytes)
+ {
+ BDRVQcow2State *s = bs->opaque;
+ QCowL2Meta *m = NULL;
+@@ -2037,7 +2037,7 @@ int qcow2_cluster_discard(BlockDriverState *bs, uint64_t offset,
+ * We don't need to allocate a QCowL2Meta for the discard operation because
+ * s->lock is held for the duration of the whole operation.
+ */
+- wait_for_dependencies(bs, offset, bytes);
++ qcow2_wait_for_dependencies(bs, offset, bytes);
+
+ /* Caller must pass aligned values, except at image end */
+ assert(QEMU_IS_ALIGNED(offset, s->cluster_size));
+@@ -2208,7 +2208,7 @@ int coroutine_fn qcow2_subcluster_zeroize(BlockDriverState *bs, uint64_t offset,
+ * We don't need to allocate a QCowL2Meta for the zeroize operation because
+ * s->lock is held for the duration of the whole operation.
+ */
+- wait_for_dependencies(bs, offset, bytes);
++ qcow2_wait_for_dependencies(bs, offset, bytes);
+
+ /* If we have to stay in sync with an external data file, zero out
+ * s->data_file first. */
+diff --git a/block/qcow2.c b/block/qcow2.c
+index a6adfa9f84..0f9818528d 100644
+--- a/block/qcow2.c
++++ b/block/qcow2.c
+@@ -4183,10 +4183,16 @@ qcow2_co_pwrite_zeroes(BlockDriverState *bs, int64_t offset, int64_t bytes,
+ }
+
+ qemu_co_mutex_lock(&s->lock);
+- /* We can have new write after previous check */
+ offset -= head;
+ bytes = s->subcluster_size;
+ nr = s->subcluster_size;
++ /*
++ * Wait for in-flight allocating writes first: otherwise the type
++ * check below could pass on UNALLOCATED while a yet-to-link_l2 write
++ * completes during qcow2_subcluster_zeroize()'s own wait, letting the
++ * resumed MAY_UNMAP discard the just-written data.
++ */
++ qcow2_wait_for_dependencies(bs, offset, bytes);
+ ret = qcow2_get_host_offset(bs, offset, &nr, &off, &type);
+ if (ret < 0 ||
+ (type != QCOW2_SUBCLUSTER_UNALLOCATED_PLAIN &&
+diff --git a/block/qcow2.h b/block/qcow2.h
+index 3e38bccd87..26fe12ab2a 100644
+--- a/block/qcow2.h
++++ b/block/qcow2.h
+@@ -961,6 +961,10 @@ int coroutine_fn GRAPH_RDLOCK
+ qcow2_subcluster_zeroize(BlockDriverState *bs, uint64_t offset, uint64_t bytes,
+ int flags);
+
++void coroutine_mixed_fn
++qcow2_wait_for_dependencies(BlockDriverState *bs, uint64_t guest_offset,
++ uint64_t bytes);
++
+ int GRAPH_RDLOCK
+ qcow2_expand_zero_clusters(BlockDriverState *bs,
+ BlockDriverAmendStatusCB *status_cb,
+diff --git a/tests/qemu-iotests/046 b/tests/qemu-iotests/046
+index e03dd40147..0d84b5c1c7 100755
+--- a/tests/qemu-iotests/046
++++ b/tests/qemu-iotests/046
+@@ -226,6 +226,26 @@ aio_write -z 0x140000 0x10000
+ resume A
+ aio_flush
+ EOF
++
++# Start an allocating write to a previously unallocated cluster and, before
++# its L2 update is linked, issue a concurrent sub-cluster zero write with
++# MAY_UNMAP that targets a disjoint range within the same cluster. The zero
++# write's head/tail are zero (cluster is unallocated), so qcow2_co_pwrite_zeroes
++# would expand it to the full subcluster. Without waiting for dependencies
++# before the zero write's "unallocated" type check, that check passes,
++# qcow2_subcluster_zeroize then yields in wait_for_dependencies, the allocating
++# write links its L2 entry, and the resumed zeroize unmaps the cluster -
++# silently discarding the just-written data. Waiting first makes the zero write
++# fall back to a bounce-buffered real write, which only touches its own
++# subrange.
++cat <<EOF
++break write_aio A
++aio_write -P 180 0x200000 0x4000
++wait_break A
++aio_write -z -u 0x204000 0x4000
++resume A
++aio_flush
++EOF
+ }
+
+ overlay_io | $QEMU_IO blkdebug::"$TEST_IMG" | _filter_qemu_io |\
+@@ -310,6 +330,9 @@ verify_io()
+ echo read -P 0 0x120000 0x10000
+ echo read -P 0 0x130000 0x10000
+ echo read -P 0 0x140000 0x10000
++
++ echo read -P 180 0x200000 0x4000
++ echo read -P 0 0x204000 0xc000
+ }
+
+ verify_io | $QEMU_IO "$TEST_IMG" | _filter_qemu_io
+diff --git a/tests/qemu-iotests/046.out b/tests/qemu-iotests/046.out
+index 6341df335c..137cf527f1 100644
+--- a/tests/qemu-iotests/046.out
++++ b/tests/qemu-iotests/046.out
+@@ -169,6 +169,12 @@ wrote XXX/XXX bytes at offset XXX
+ XXX KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+ wrote XXX/XXX bytes at offset XXX
+ XXX KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
++blkdebug: Suspended request 'A'
++blkdebug: Resuming request 'A'
++wrote XXX/XXX bytes at offset XXX
++XXX KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
++wrote XXX/XXX bytes at offset XXX
++XXX KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+
+ == Verify image content ==
+ read 65536/65536 bytes at offset 0
+@@ -275,5 +281,9 @@ read 65536/65536 bytes at offset 1245184
+ 64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+ read 65536/65536 bytes at offset 1310720
+ 64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
++read 16384/16384 bytes at offset 2097152
++16 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
++read 49152/49152 bytes at offset 2113536
++48 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+ No errors were found on the image.
+ *** done
+--
+2.52.0
+
diff --git a/kvm-qed-Don-t-try-to-flush-during-incoming-migration.patch b/kvm-qed-Don-t-try-to-flush-during-incoming-migration.patch
new file mode 100644
index 0000000..6954e4f
--- /dev/null
+++ b/kvm-qed-Don-t-try-to-flush-during-incoming-migration.patch
@@ -0,0 +1,160 @@
+From 4ed80433ccb6c0009a23dd71159af596badec426 Mon Sep 17 00:00:00 2001
+From: Fabiano Rosas <farosas@suse.de>
+Date: Sat, 13 Jun 2026 23:03:40 +0300
+Subject: [PATCH 18/52] qed: Don't try to flush during incoming migration
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
+RH-Jira: RHEL-186384
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
+RH-Commit: [11/45] 2a884395bec8d06c70656664d87fe03e6e7ba902 (kmwolf/centos-qemu-kvm)
+
+It's not possible to access the image file while there is an incoming
+migration in progress, the QEMU process doesn't hold any locks to the
+storage at this point so nodes are inactive. Attempting to flush leads
+to an assert at bdrv_co_write_req_prepare():
+
+ assert(!(bs->open_flags & BDRV_O_INACTIVE))
+
+The issue is reproducible by running iotest 181 on a host under cpu
+load. The migration must coincide with the header already containing
+the QED_F_NEED_CHECK flag.
+
+The sequence of events is as follows, with the respective call stacks
+referenced below:
+
+During block device init, bdrv_qed_attach_aio_context() starts the
+'need_check' timer. The timer will not fire during incoming migration
+as it uses QEMU_CLOCK_VIRTUAL (to avoid this very issue, as the code
+comment indicates). (0)
+
+However, there's still bdrv_qed_drain_begin() which uses the fact that
+the timer is live to decide whether to start the
+qed_need_check_timer_entry() directly. (1)
+
+The qed_need_check_timer_entry() eventually calls into
+qed_write_header() -> bdrv_co_pwrite() leading to the assert. (2)
+
+Skip creating the 'need_check' timer whenever the image is inactive.
+
+The stacks:
+
+(0) == issues timer_mod ==
+ #6 in qed_start_need_check_timer at ../block/qed.c:340
+ #7 in bdrv_qed_attach_aio_context at ../block/qed.c:373
+ #8 in bdrv_qed_do_open at ../block/qed.c:556
+ #9 in bdrv_qed_open_entry at ../block/qed.c:582
+ #10 in coroutine_trampoline at ../util/coroutine-ucontext.c:175
+ #0 in qemu_coroutine_switch<+120> at ../util/coroutine-ucontext.c:321
+ #1 in qemu_aio_coroutine_enter<+356> at ../util/qemu-coroutine.c:293
+ #2 in aio_co_enter<+179> at ../util/async.c:710
+ #3 in aio_co_wake<+53> at ../util/async.c:695
+ #4 in thread_pool_co_cb<+47> at ../util/thread-pool.c:283
+ #5 in thread_pool_completion_bh<+241> at ../util/thread-pool.c:202
+ #6 in aio_bh_call<+109> at ../util/async.c:173
+ #7 in aio_bh_poll<+299> at ../util/async.c:220
+ #8 in aio_poll<+690> at ../util/aio-posix.c:745
+ #9 in bdrv_qed_open<+392> at ../block/qed.c:607
+ #10 in bdrv_open_driver<+327> at ../block.c:1678
+ #11 in bdrv_open_common<+1619> at ../block.c:2008
+ #12 in bdrv_open_inherit<+2556> at ../block.c:4191
+ #13 in bdrv_open<+118> at ../block.c:4286
+ #14 in blk_new_open<+199> at ../block/block-backend.c:458
+ #15 in blockdev_init<+2011> at ../blockdev.c:612
+ #16 in drive_new<+3008> at ../blockdev.c:1008
+ #17 in drive_init_func<+51> at ../system/vl.c:662
+ #18 in qemu_opts_foreach<+227> at ../util/qemu-option.c:1148
+ #19 in configure_blockdev<+350> at ../system/vl.c:721
+ #20 in qemu_create_early_backends<+343> at ../system/vl.c:2076
+ #21 in qemu_init<+12483> at ../system/vl.c:3778
+ #22 in main<+46> at ../system/main.c:71
+
+(1) == sees timer_pending ==
+ #6 in bdrv_qed_drain_begin at ../block/qed.c:391
+ #7 in bdrv_do_drained_begin at ../block/io.c:366
+ #8 in bdrv_do_drained_begin_quiesce at ../block/io.c:386
+ #9 in bdrv_child_cb_drained_begin at ../block.c:1207
+ #10 in bdrv_parent_drained_begin_single at ../block/io.c:133
+ #11 in bdrv_parent_drained_begin at ../block/io.c:64
+ #12 in bdrv_do_drained_begin at ../block/io.c:364
+ #13 in bdrv_drained_begin at ../block/io.c:393
+ #14 in blk_drain at ../block/block-backend.c:2101
+ #15 in blk_unref at ../block/block-backend.c:544
+ #16 in bdrv_open_inherit at ../block.c:4197
+ #17 in bdrv_open at ../block.c:4286
+ #18 in blk_new_open at ../block/block-backend.c:458
+ #19 in blockdev_init at ../blockdev.c:612
+ #20 in drive_new at ../blockdev.c:1008
+ #21 in drive_init_func at ../system/vl.c:662
+ #22 in qemu_opts_foreach at ../util/qemu-option.c:1148
+ #23 in configure_blockdev at ../system/vl.c:721
+ #24 in qemu_create_early_backends at ../system/vl.c:2076
+ #25 in qemu_init at ../system/vl.c:3778
+ #26 in main at ../system/main.c:71
+
+(2) == crashes ==
+ #5 in __assert_fail (assertion="!(bs->open_flags & BDRV_O_INACTIVE)", file="../block/io.c", line=1977
+ #6 in bdrv_co_write_req_prepare at ../block/io.c:1977
+ #7 in bdrv_aligned_pwritev at ../block/io.c:2099
+ #8 in bdrv_co_pwritev_part at ../block/io.c:2316
+ #9 in bdrv_co_pwritev at ../block/io.c:2233
+ #10 in bdrv_co_pwrite at ../include/block/block_int-io.h:77
+ #11 in qed_write_header at ../block/qed.c:128
+ #12 in qed_need_check_timer at ../block/qed.c:305
+ #13 in qed_need_check_timer_entry at ../block/qed.c:319
+
+Note that this issue is not exactly the same as what's been reported
+in Gitlab, but given how easily this reproduces, I imagine it has to
+be happening in that setup as well.
+
+Link: https://gitlab.com/qemu-project/qemu/-/work_items/3515
+Signed-off-by: Fabiano Rosas <farosas@suse.de>
+Message-ID: <20260603193813.2327596-1-farosas@suse.de>
+Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
+Reviewed-by: Kevin Wolf <kwolf@redhat.com>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+(cherry picked from commit 7e573b660fefdebd21cb755d0d34bb5942fd3af3)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+
+Message-ID: <20260613200411.1808021-57-mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ block/qed.c | 16 +++++++++++-----
+ 1 file changed, 11 insertions(+), 5 deletions(-)
+
+diff --git a/block/qed.c b/block/qed.c
+index 4a36fb3929..917b8d91b5 100644
+--- a/block/qed.c
++++ b/block/qed.c
+@@ -351,16 +351,22 @@ static void bdrv_qed_detach_aio_context(BlockDriverState *bs)
+ {
+ BDRVQEDState *s = bs->opaque;
+
+- qed_cancel_need_check_timer(s);
+- timer_free(s->need_check_timer);
+- s->need_check_timer = NULL;
++ if (s->need_check_timer) {
++ qed_cancel_need_check_timer(s);
++ timer_free(s->need_check_timer);
++ s->need_check_timer = NULL;
++ }
+ }
+
+-static void bdrv_qed_attach_aio_context(BlockDriverState *bs,
+- AioContext *new_context)
++static void GRAPH_RDLOCK bdrv_qed_attach_aio_context(BlockDriverState *bs,
++ AioContext *new_context)
+ {
+ BDRVQEDState *s = bs->opaque;
+
++ if (bdrv_is_inactive(bs)) {
++ return;
++ }
++
+ s->need_check_timer = aio_timer_new(new_context,
+ QEMU_CLOCK_VIRTUAL, SCALE_NS,
+ qed_need_check_timer_cb, s);
+--
+2.52.0
+
diff --git a/kvm-qemu-img-Fix-amend-option-parse-error-handling.patch b/kvm-qemu-img-Fix-amend-option-parse-error-handling.patch
new file mode 100644
index 0000000..de2c975
--- /dev/null
+++ b/kvm-qemu-img-Fix-amend-option-parse-error-handling.patch
@@ -0,0 +1,49 @@
+From a0454a8af25172f3f1f0f6eacc3e07d9c2b077fe Mon Sep 17 00:00:00 2001
+From: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>
+Date: Thu, 23 Oct 2025 17:10:58 +0900
+Subject: [PATCH 22/52] qemu-img: Fix amend option parse error handling
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
+RH-Jira: RHEL-186384
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
+RH-Commit: [15/45] 07da070d4ecdcb1ad22761fe68038b85dc0d0a42 (kmwolf/centos-qemu-kvm)
+
+qemu_opts_del(opts) dereferences opts->list, which is the old amend_opts
+pointer that can be dangling after executing
+qemu_opts_append(amend_opts, bs->drv->create_opts) and cause
+use-after-free.
+
+Fix the potential use-after-free by moving the qemu_opts_del() call
+before the qemu_opts_append() call.
+
+Signed-off-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>
+Message-ID: <20251023-iotests-v1-1-fab143ca4c2f@rsg.ci.i.u-tokyo.ac.jp>
+Reviewed-by: Kevin Wolf <kwolf@redhat.com>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+(cherry picked from commit f00bcc833790c72c08bc5eed97845fdaa7542507)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ qemu-img.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/qemu-img.c b/qemu-img.c
+index a7791896c1..7a32d2d16c 100644
+--- a/qemu-img.c
++++ b/qemu-img.c
+@@ -4597,9 +4597,9 @@ static int img_amend(const img_cmd_t *ccmd, int argc, char **argv)
+ amend_opts = qemu_opts_append(amend_opts, bs->drv->amend_opts);
+ opts = qemu_opts_create(amend_opts, NULL, 0, &error_abort);
+ if (!qemu_opts_do_parse(opts, options, NULL, &err)) {
++ qemu_opts_del(opts);
+ /* Try to parse options using the create options */
+ amend_opts = qemu_opts_append(amend_opts, bs->drv->create_opts);
+- qemu_opts_del(opts);
+ opts = qemu_opts_create(amend_opts, NULL, 0, &error_abort);
+ if (qemu_opts_do_parse(opts, options, NULL, NULL)) {
+ error_append_hint(&err,
+--
+2.52.0
+
diff --git a/kvm-qemu-img-rebase-don-t-exceed-IO_BUF_SIZE-in-one-oper.patch b/kvm-qemu-img-rebase-don-t-exceed-IO_BUF_SIZE-in-one-oper.patch
new file mode 100644
index 0000000..44f3541
--- /dev/null
+++ b/kvm-qemu-img-rebase-don-t-exceed-IO_BUF_SIZE-in-one-oper.patch
@@ -0,0 +1,161 @@
+From 21f29bc69a9ce910c6869453a48665087fddfa29 Mon Sep 17 00:00:00 2001
+From: Alberto Garcia <berto@igalia.com>
+Date: Fri, 7 Nov 2025 10:18:30 +0100
+Subject: [PATCH 23/52] qemu-img rebase: don't exceed IO_BUF_SIZE in one
+ operation
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
+RH-Jira: RHEL-186384
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
+RH-Commit: [16/45] c0b0720c3a75eeb7a1519e1df29ffb9a59adc531 (kmwolf/centos-qemu-kvm)
+
+During a rebase operation data is copied from the backing chain into
+the target image using a loop, and each iteration looks for a
+contiguous region of allocated data of at most IO_BUF_SIZE (2 MB).
+
+Once that region is found, and in order to avoid partial writes, its
+boundaries are extended so they are aligned to the (sub)clusters of
+the target image (see commit 12df580b).
+
+This operation can however result in a region that exceeds the maximum
+allowed IO_BUF_SIZE, crashing qemu-img.
+
+This can be easily reproduced when the source image has a smaller
+cluster size than the target image:
+
+base <- int <- active
+
+$ qemu-img create -f qcow2 base.qcow2 4M
+$ qemu-img create -f qcow2 -F qcow2 -b base.qcow2 -o cluster_size=1M int.qcow2
+$ qemu-img create -f qcow2 -F qcow2 -b int.qcow2 -o cluster_size=2M active.qcow2
+$ qemu-io -c "write -P 0xff 1M 2M" int.qcow2
+$ qemu-img rebase -F qcow2 -b base.qcow2 active.qcow2
+qemu-img: qemu-img.c:4102: img_rebase: Assertion `written + pnum <= IO_BUF_SIZE' failed.
+Aborted
+
+Cc: qemu-stable <qemu-stable@nongnu.org>
+Resolves: https://gitlab.com/qemu-project/qemu/-/issues/3174
+Fixes: 12df580b3b7f ("qemu-img: rebase: avoid unnecessary COW operations")
+Signed-off-by: Alberto Garcia <berto@igalia.com>
+Message-ID: <20251107091834.383781-1-berto@igalia.com>
+Reviewed-by: Kevin Wolf <kwolf@redhat.com>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+(cherry picked from commit 909852ba6b4a22fd2b6f9d8b88adb5fc47dfa781)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ qemu-img.c | 2 +-
+ tests/qemu-iotests/024 | 46 ++++++++++++++++++++++++++++++++++++++
+ tests/qemu-iotests/024.out | 26 +++++++++++++++++++++
+ 3 files changed, 73 insertions(+), 1 deletion(-)
+
+diff --git a/qemu-img.c b/qemu-img.c
+index 7a32d2d16c..c42dd4e995 100644
+--- a/qemu-img.c
++++ b/qemu-img.c
+@@ -4081,7 +4081,7 @@ static int img_rebase(const img_cmd_t *ccmd, int argc, char **argv)
+ n += offset - QEMU_ALIGN_DOWN(offset, write_align);
+ offset = QEMU_ALIGN_DOWN(offset, write_align);
+ n += QEMU_ALIGN_UP(offset + n, write_align) - (offset + n);
+- n = MIN(n, size - offset);
++ n = MIN(n, MIN(size - offset, IO_BUF_SIZE));
+ assert(!bdrv_is_allocated(unfiltered_bs, offset, n, &n_alloc) &&
+ n_alloc == n);
+
+diff --git a/tests/qemu-iotests/024 b/tests/qemu-iotests/024
+index b29c76e161..021169b4a1 100755
+--- a/tests/qemu-iotests/024
++++ b/tests/qemu-iotests/024
+@@ -315,6 +315,52 @@ echo
+
+ $QEMU_IMG map "$OVERLAY" | _filter_qemu_img_map
+
++# Check that the region to copy to the overlay during a rebase
++# operation does not exceed the I/O buffer size.
++#
++# backing_new <-- backing_old <-- overlay
++#
++# Backing (new): -- -- -- -- <-- Empty image, size 4MB
++# Backing (old):|--|ff|ff|--| <-- 4 clusters, 1MB each
++# Overlay: |-- --|-- --| <-- 2 clusters, 2MB each
++#
++# The data at [1MB, 3MB) must be copied from the old backing image to
++# the overlay. However the rebase code will extend that region to the
++# overlay's (sub)cluster boundaries to avoid CoW (see commit 12df580b).
++# This test checks that IO_BUF_SIZE (2 MB) is taken into account.
++
++echo
++echo "=== Test that the region to copy does not exceed 2MB (IO_BUF_SIZE) ==="
++echo
++
++echo "Creating backing chain"
++echo
++
++TEST_IMG=$BASE_NEW _make_test_img 4M
++TEST_IMG=$BASE_OLD CLUSTER_SIZE=1M _make_test_img -b "$BASE_NEW" -F $IMGFMT
++TEST_IMG=$OVERLAY CLUSTER_SIZE=2M _make_test_img -b "$BASE_OLD" -F $IMGFMT
++
++echo
++echo "Writing data to region [1MB, 3MB)"
++echo
++
++$QEMU_IO "$BASE_OLD" -c "write -P 0xff 1M 2M" | _filter_qemu_io
++
++echo
++echo "Rebasing"
++echo
++
++$QEMU_IMG rebase -b "$BASE_NEW" -F $IMGFMT "$OVERLAY"
++
++echo "Verifying the data"
++echo
++
++$QEMU_IO "$OVERLAY" -c "read -P 0x00 0 1M" | _filter_qemu_io
++$QEMU_IO "$OVERLAY" -c "read -P 0xff 1M 2M" | _filter_qemu_io
++$QEMU_IO "$OVERLAY" -c "read -P 0x00 3M 1M" | _filter_qemu_io
++
++$QEMU_IMG map "$OVERLAY" | _filter_qemu_img_map
++
+ echo
+
+ # success, all done
+diff --git a/tests/qemu-iotests/024.out b/tests/qemu-iotests/024.out
+index 3d1e31927a..1b7522ba71 100644
+--- a/tests/qemu-iotests/024.out
++++ b/tests/qemu-iotests/024.out
+@@ -243,4 +243,30 @@ Offset Length File
+ 0 0x20000 TEST_DIR/subdir/t.IMGFMT
+ 0x40000 0x20000 TEST_DIR/subdir/t.IMGFMT
+
++=== Test that the region to copy does not exceed 2MB (IO_BUF_SIZE) ===
++
++Creating backing chain
++
++Formatting 'TEST_DIR/subdir/t.IMGFMT.base_new', fmt=IMGFMT size=4194304
++Formatting 'TEST_DIR/subdir/t.IMGFMT.base_old', fmt=IMGFMT size=4194304 backing_file=TEST_DIR/subdir/t.IMGFMT.base_new backing_fmt=IMGFMT
++Formatting 'TEST_DIR/subdir/t.IMGFMT', fmt=IMGFMT size=4194304 backing_file=TEST_DIR/subdir/t.IMGFMT.base_old backing_fmt=IMGFMT
++
++Writing data to region [1MB, 3MB)
++
++wrote 2097152/2097152 bytes at offset 1048576
++2 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
++
++Rebasing
++
++Verifying the data
++
++read 1048576/1048576 bytes at offset 0
++1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
++read 2097152/2097152 bytes at offset 1048576
++2 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
++read 1048576/1048576 bytes at offset 3145728
++1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
++Offset Length File
++0 0x400000 TEST_DIR/subdir/t.IMGFMT
++
+ *** done
+--
+2.52.0
+
diff --git a/kvm-qemu-io-Add-aio_discard-command.patch b/kvm-qemu-io-Add-aio_discard-command.patch
new file mode 100644
index 0000000..14b744d
--- /dev/null
+++ b/kvm-qemu-io-Add-aio_discard-command.patch
@@ -0,0 +1,167 @@
+From f598b8e008e2b9179acaa138d75bac390a204508 Mon Sep 17 00:00:00 2001
+From: Kevin Wolf <kwolf@redhat.com>
+Date: Sat, 13 Jun 2026 23:03:36 +0300
+Subject: [PATCH 48/52] qemu-io: Add 'aio_discard' command
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
+RH-Jira: RHEL-186384
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
+RH-Commit: [41/45] f10ea407dd2cb93550396230790802dc8a728774 (kmwolf/centos-qemu-kvm)
+
+Testing interactions between multiple requests that include discard
+requests require that qemu-io can do the discard asynchronously, like it
+already does for reads and writes. To this effect, add an 'aio_discard'
+command.
+
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+Message-ID: <20260427170520.101242-3-kwolf@redhat.com>
+Reviewed-by: Denis V. Lunev <den@openvz.org>
+Tested-by: Denis V. Lunev <den@openvz.org>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+(cherry picked from commit 7f8466e2ce620e3c6a6e2f32d616367174d4dbe9)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+
+Message-ID: <20260613200411.1808021-53-mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ qemu-io-cmds.c | 115 +++++++++++++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 115 insertions(+)
+
+diff --git a/qemu-io-cmds.c b/qemu-io-cmds.c
+index f6d077908f..de4c1966fe 100644
+--- a/qemu-io-cmds.c
++++ b/qemu-io-cmds.c
+@@ -2218,6 +2218,120 @@ static int discard_f(BlockBackend *blk, int argc, char **argv)
+ return 0;
+ }
+
++static void aio_discard_help(void)
++{
++ printf(
++"\n"
++" asynchronously discards a range of bytes from the given offset\n"
++"\n"
++" Example:\n"
++" 'aio_discard 512 1k' - discards 1 kilobyte from 512 bytes into the file\n"
++"\n"
++" Discards a segment of the currently open file.\n"
++" -C, -- report statistics in a machine parsable format\n"
++" -q, -- quiet mode, do not show I/O statistics\n"
++" The discard is performed asynchronously and the aio_flush command must be\n"
++" used to ensure all outstanding aio requests have been completed.\n"
++" Note that due to its asynchronous nature, this command will be\n"
++" considered successful once the request is submitted, independently\n"
++" of potential I/O errors.\n"
++"\n");
++}
++
++static int aio_discard_f(BlockBackend *blk, int argc, char **argv);
++
++static const cmdinfo_t aio_discard_cmd = {
++ .name = "aio_discard",
++ .cfunc = aio_discard_f,
++ .perm = BLK_PERM_WRITE,
++ .argmin = 2,
++ .argmax = -1,
++ .args = "[-Cq] off len",
++ .oneline = "asynchronously discards a number of bytes",
++ .help = aio_discard_help,
++};
++
++static void aio_discard_done(void *opaque, int ret)
++{
++ struct aio_ctx *ctx = opaque;
++ struct timespec t2;
++
++ clock_gettime(CLOCK_MONOTONIC, &t2);
++
++ if (ret < 0) {
++ printf("aio_discard failed: %s\n", strerror(-ret));
++ block_acct_failed(blk_get_stats(ctx->blk), &ctx->acct);
++ goto out;
++ }
++
++ block_acct_done(blk_get_stats(ctx->blk), &ctx->acct);
++
++ if (ctx->qflag) {
++ goto out;
++ }
++
++ /* Finally, report back -- -C gives a parsable format */
++ t2 = tsub(t2, ctx->t1);
++ print_report("discarded ", &t2, ctx->offset, ctx->qiov.size,
++ ctx->qiov.size, 1, ctx->Cflag);
++out:
++ g_free(ctx);
++}
++
++static int aio_discard_f(BlockBackend *blk, int argc, char **argv)
++{
++ int c, ret;
++ int64_t count;
++ struct aio_ctx *ctx = g_new0(struct aio_ctx, 1);
++
++ ctx->blk = blk;
++
++ while ((c = getopt(argc, argv, "Cq")) != -1) {
++ switch (c) {
++ case 'C':
++ ctx->Cflag = true;
++ break;
++ case 'q':
++ ctx->qflag = true;
++ break;
++ default:
++ g_free(ctx);
++ qemuio_command_usage(&aio_discard_cmd);
++ return -EINVAL;
++ }
++ }
++
++ if (optind != argc - 2) {
++ g_free(ctx);
++ qemuio_command_usage(&aio_discard_cmd);
++ return -EINVAL;
++ }
++
++ ctx->offset = cvtnum(argv[optind]);
++ if (ctx->offset < 0) {
++ ret = ctx->offset;
++ print_cvtnum_err(ret, argv[optind]);
++ g_free(ctx);
++ return ret;
++ }
++ optind++;
++
++ count = cvtnum(argv[optind]);
++ if (count < 0) {
++ print_cvtnum_err(count, argv[optind]);
++ g_free(ctx);
++ return count;
++ }
++
++ clock_gettime(CLOCK_MONOTONIC, &ctx->t1);
++ ctx->qiov.size = count;
++ block_acct_start(blk_get_stats(blk), &ctx->acct, ctx->qiov.size,
++ BLOCK_ACCT_UNMAP);
++ blk_aio_pdiscard(blk, ctx->offset, count, aio_discard_done, ctx);
++
++ return 0;
++}
++
+ static int alloc_f(BlockBackend *blk, int argc, char **argv)
+ {
+ BlockDriverState *bs = blk_bs(blk);
+@@ -2800,6 +2914,7 @@ static void __attribute((constructor)) init_qemuio_commands(void)
+ qemuio_add_command(&length_cmd);
+ qemuio_add_command(&info_cmd);
+ qemuio_add_command(&discard_cmd);
++ qemuio_add_command(&aio_discard_cmd);
+ qemuio_add_command(&alloc_cmd);
+ qemuio_add_command(&map_cmd);
+ qemuio_add_command(&reopen_cmd);
+--
+2.52.0
+
diff --git a/kvm-tests-qemu-iotest-fix-iotest-024-with-qed-images.patch b/kvm-tests-qemu-iotest-fix-iotest-024-with-qed-images.patch
new file mode 100644
index 0000000..7abc314
--- /dev/null
+++ b/kvm-tests-qemu-iotest-fix-iotest-024-with-qed-images.patch
@@ -0,0 +1,60 @@
+From 327ef6e8b3ea224047409dea90edba5c59f174ad Mon Sep 17 00:00:00 2001
+From: Alberto Garcia <berto@igalia.com>
+Date: Wed, 12 Nov 2025 18:09:57 +0100
+Subject: [PATCH 29/52] tests/qemu-iotest: fix iotest 024 with qed images
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
+RH-Jira: RHEL-186384
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
+RH-Commit: [22/45] ec515379b04946e4730812abccc7d72ff6c7df26 (kmwolf/centos-qemu-kvm)
+
+Use 'qemu-io -c map' instead of 'qemu-img map' to get an output that
+works with both image types.
+
+Cc: qemu-stable <qemu-stable@nongnu.org>
+Fixes: 909852ba6b4a ("qemu-img rebase: don't exceed IO_BUF_SIZE in one operation")
+Fixes: 26b17e34bedb ("qemu-img rebase: don't exceed IO_BUF_SIZE in one operation") in 10.1.x
+Signed-off-by: Alberto Garcia <berto@igalia.com>
+Message-ID: <20251112170959.700840-1-berto@igalia.com>
+Reviewed-by: Eric Blake <eblake@redhat.com>
+Tested-by: Thomas Huth <thuth@redhat.com>
+Signed-off-by: Eric Blake <eblake@redhat.com>
+(cherry picked from commit 4c91719a6a78a1c24d8bb854f7594e767962d0d9)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ tests/qemu-iotests/024 | 2 +-
+ tests/qemu-iotests/024.out | 3 +--
+ 2 files changed, 2 insertions(+), 3 deletions(-)
+
+diff --git a/tests/qemu-iotests/024 b/tests/qemu-iotests/024
+index 021169b4a1..10be2bd845 100755
+--- a/tests/qemu-iotests/024
++++ b/tests/qemu-iotests/024
+@@ -359,7 +359,7 @@ $QEMU_IO "$OVERLAY" -c "read -P 0x00 0 1M" | _filter_qemu_io
+ $QEMU_IO "$OVERLAY" -c "read -P 0xff 1M 2M" | _filter_qemu_io
+ $QEMU_IO "$OVERLAY" -c "read -P 0x00 3M 1M" | _filter_qemu_io
+
+-$QEMU_IMG map "$OVERLAY" | _filter_qemu_img_map
++$QEMU_IO -c map "$OVERLAY" | _filter_qemu_io
+
+ echo
+
+diff --git a/tests/qemu-iotests/024.out b/tests/qemu-iotests/024.out
+index 1b7522ba71..da8fedc08b 100644
+--- a/tests/qemu-iotests/024.out
++++ b/tests/qemu-iotests/024.out
+@@ -266,7 +266,6 @@ read 2097152/2097152 bytes at offset 1048576
+ 2 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+ read 1048576/1048576 bytes at offset 3145728
+ 1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+-Offset Length File
+-0 0x400000 TEST_DIR/subdir/t.IMGFMT
++4 MiB (0x400000) bytes allocated at offset 0 bytes (0x0)
+
+ *** done
+--
+2.52.0
+
diff --git a/kvm-tests-qemu-iotests-Fix-check-for-existing-file-in-_r.patch b/kvm-tests-qemu-iotests-Fix-check-for-existing-file-in-_r.patch
new file mode 100644
index 0000000..1d961f6
--- /dev/null
+++ b/kvm-tests-qemu-iotests-Fix-check-for-existing-file-in-_r.patch
@@ -0,0 +1,49 @@
+From 588224f69f23d255c577ddc6fabcdb4288c420b0 Mon Sep 17 00:00:00 2001
+From: Thomas Huth <thuth@redhat.com>
+Date: Mon, 8 Dec 2025 08:53:20 +0100
+Subject: [PATCH 30/52] tests/qemu-iotests: Fix check for existing file in
+ _require_disk_usage()
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
+RH-Jira: RHEL-186384
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
+RH-Commit: [23/45] e757b9e7b5fb75c2093d1a7a85332188dd4daa38 (kmwolf/centos-qemu-kvm)
+
+Looks like the "$" has been forgotten here to get the contents of
+the FILENAME variable.
+
+Fixes: c49dda7254d ("iotests: Filter out ZFS in several tests")
+Signed-off-by: Thomas Huth <thuth@redhat.com>
+Message-ID: <20251208075320.35682-1-thuth@redhat.com>
+Reviewed-by: Laurent Vivier <laurent@vivier.eu>
+Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
+Reviewed-by: Kevin Wolf <kwolf@redhat.com>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+(cherry picked from commit ab0065e36adf8becd9c1ffceec37ee809ce683af)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ tests/qemu-iotests/common.rc | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/tests/qemu-iotests/common.rc b/tests/qemu-iotests/common.rc
+index e977cb4eb6..d8fe476806 100644
+--- a/tests/qemu-iotests/common.rc
++++ b/tests/qemu-iotests/common.rc
+@@ -1008,7 +1008,7 @@ _require_disk_usage()
+ else
+ FILENAME="$TEST_IMG_FILE"
+ fi
+- if [ -e "FILENAME" ]; then
++ if [ -e "$FILENAME" ]; then
+ echo "unwilling to overwrite existing file"
+ exit 1
+ fi
+--
+2.52.0
+
diff --git a/kvm-throttle-group-Fix-race-condition-in-throttle_group_.patch b/kvm-throttle-group-Fix-race-condition-in-throttle_group_.patch
new file mode 100644
index 0000000..87a359e
--- /dev/null
+++ b/kvm-throttle-group-Fix-race-condition-in-throttle_group_.patch
@@ -0,0 +1,179 @@
+From 8340b5280edd665c0b1bc6d11c28099252f92d8a Mon Sep 17 00:00:00 2001
+From: Alberto Garcia <berto@igalia.com>
+Date: Thu, 12 Mar 2026 13:12:00 +0100
+Subject: [PATCH 21/52] throttle-group: Fix race condition in
+ throttle_group_restart_queue()
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
+RH-Jira: RHEL-186384
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
+RH-Commit: [14/45] 695ff7204a816025291b74409abfdff6963075f4 (kmwolf/centos-qemu-kvm)
+
+When a timer is fired a pending I/O request is restarted and
+tg->any_timer_armed is reset so other requests can be scheduled.
+
+However we're resetting any_timer_armed first in timer_cb() before
+the request is actually restarted, and there's a window between both
+moments in which another thread can arm the same timer, hitting an
+assertion in throttle_group_restart_queue().
+
+This can be solved by deferring the reset of tg->any_timer_armed to
+the moment when the queue is actually restarted, which is protected by
+tg->lock, preventing other threads from arming the timer before that.
+
+In addition to that, throttle_group_restart_tgm() is also updated to
+hold tg->lock while the timer is being inspected. Here we consider
+three different scenarios:
+
+- If the tgm has a timer set, fire it immediately
+- If another tgm has a timer set, restart the queue anyway
+- If there is no timer set in this group then simulate a timer that
+ fires immediately, by setting tg->any_timer_armed in order to
+ prevent other threads from arming a timer in the meantime.
+
+Resolves: https://gitlab.com/qemu-project/qemu/-/issues/3194
+Signed-off-by: Alberto Garcia <berto@igalia.com>
+Message-Id: <825598ef34ad384d936da19d634eda75598508f7.1773316842.git.berto@igalia.com>
+Signed-off-by: Hanna Czenczek <hreitz@redhat.com>
+(cherry picked from commit 9c8430f5d65144b85ad76433369288182a1c7baa)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ block/throttle-groups.c | 79 +++++++++++++++++++++++++++++++----------
+ 1 file changed, 60 insertions(+), 19 deletions(-)
+
+diff --git a/block/throttle-groups.c b/block/throttle-groups.c
+index 5329ff1fdb..4b1b1944c2 100644
+--- a/block/throttle-groups.c
++++ b/block/throttle-groups.c
+@@ -391,6 +391,7 @@ void coroutine_fn throttle_group_co_io_limits_intercept(ThrottleGroupMember *tgm
+ typedef struct {
+ ThrottleGroupMember *tgm;
+ ThrottleDirection direction;
++ bool reset_timer_armed;
+ } RestartData;
+
+ static void coroutine_fn throttle_group_restart_queue_entry(void *opaque)
+@@ -403,6 +404,9 @@ static void coroutine_fn throttle_group_restart_queue_entry(void *opaque)
+ bool empty_queue;
+
+ qemu_mutex_lock(&tg->lock);
++ if (data->reset_timer_armed) {
++ tg->any_timer_armed[direction] = false;
++ }
+ empty_queue = !throttle_group_co_restart_queue(tgm, direction);
+
+ /* If the request queue was empty then we have to take care of
+@@ -419,18 +423,23 @@ static void coroutine_fn throttle_group_restart_queue_entry(void *opaque)
+ }
+
+ static void throttle_group_restart_queue(ThrottleGroupMember *tgm,
+- ThrottleDirection direction)
++ ThrottleDirection direction,
++ bool reset_timer_armed)
+ {
+ Coroutine *co;
+ RestartData *rd = g_new0(RestartData, 1);
+
+ rd->tgm = tgm;
+ rd->direction = direction;
++ rd->reset_timer_armed = reset_timer_armed;
+
+- /* This function is called when a timer is fired or when
+- * throttle_group_restart_tgm() is called. Either way, there can
++ /* If reset_timer_armed is set then this means that this function
++ * was called when a timer was fired (either from timer_cb() or
++ * from throttle_group_restart_tgm()). In this case there can
+ * be no timer pending on this tgm at this point */
+- assert(!timer_pending(tgm->throttle_timers.timers[direction]));
++ if (reset_timer_armed) {
++ assert(!timer_pending(tgm->throttle_timers.timers[direction]));
++ }
+
+ qatomic_inc(&tgm->restart_pending);
+
+@@ -444,15 +453,50 @@ void throttle_group_restart_tgm(ThrottleGroupMember *tgm)
+
+ if (tgm->throttle_state) {
+ for (dir = THROTTLE_READ; dir < THROTTLE_MAX; dir++) {
+- QEMUTimer *t = tgm->throttle_timers.timers[dir];
++ QEMUTimer *t;
++ ThrottleState *ts = tgm->throttle_state;
++ ThrottleGroup *tg = container_of(ts, ThrottleGroup, ts);
++ bool reset_timer_armed;
++
++ /*
++ * This function restarts the tgm's queue immediately.
++ * This is used for example for callers to drain all requests.
++ * There are three different scenarios depending on whether
++ * a timer is armed for this tg and which tgm owns the timer.
++ */
++
++ qemu_mutex_lock(&tg->lock);
++
++ t = tgm->throttle_timers.timers[dir];
+ if (timer_pending(t)) {
+- /* If there's a pending timer on this tgm, fire it now */
++ /*
++ * Case 1: this tgm has a pending timer.
++ * We can fire the timer immediately.
++ */
+ timer_del(t);
+- timer_cb(tgm, dir);
++ reset_timer_armed = true;
++ } else if (tg->any_timer_armed[dir]) {
++ /*
++ * Case 2: another tgm has a pending timer.
++ * In this case we can still restart the queue but we
++ * have to leave any_timer_armed untouched so the
++ * other tgm's timer is not disrupted.
++ */
++ reset_timer_armed = false;
+ } else {
+- /* Else run the next request from the queue manually */
+- throttle_group_restart_queue(tgm, dir);
++ /*
++ * Case 3: there is no timer set for this group.
++ * Here we can simulate a timer that fires immediately,
++ * so the queue is restarted but no other thread
++ * can arm a timer in the meantime.
++ */
++ tg->any_timer_armed[dir] = true;
++ reset_timer_armed = true;
+ }
++
++ qemu_mutex_unlock(&tg->lock);
++
++ throttle_group_restart_queue(tgm, dir, reset_timer_armed);
+ }
+ }
+ }
+@@ -499,16 +543,13 @@ void throttle_group_get_config(ThrottleGroupMember *tgm, ThrottleConfig *cfg)
+ */
+ static void timer_cb(ThrottleGroupMember *tgm, ThrottleDirection direction)
+ {
+- ThrottleState *ts = tgm->throttle_state;
+- ThrottleGroup *tg = container_of(ts, ThrottleGroup, ts);
+-
+- /* The timer has just been fired, so we can update the flag */
+- qemu_mutex_lock(&tg->lock);
+- tg->any_timer_armed[direction] = false;
+- qemu_mutex_unlock(&tg->lock);
+-
+- /* Run the request that was waiting for this timer */
+- throttle_group_restart_queue(tgm, direction);
++ /*
++ * Run the request that was waiting for this timer.
++ * tg->any_timer_armed needs to be cleared, but we'll do it later
++ * when the queue is restarted in order to prevent another thread
++ * from arming the timer before that.
++ */
++ throttle_group_restart_queue(tgm, direction, true);
+ }
+
+ static void read_timer_cb(void *opaque)
+--
+2.52.0
+
diff --git a/kvm-virtio-Fix-crash-when-sriov-pf-is-set-for-non-PCI-Ex.patch b/kvm-virtio-Fix-crash-when-sriov-pf-is-set-for-non-PCI-Ex.patch
new file mode 100644
index 0000000..0ea10be
--- /dev/null
+++ b/kvm-virtio-Fix-crash-when-sriov-pf-is-set-for-non-PCI-Ex.patch
@@ -0,0 +1,79 @@
+From e9d0e7db0e7e90fb8a71e3725cdceba7a83b665e Mon Sep 17 00:00:00 2001
+From: Kevin Wolf <kwolf@redhat.com>
+Date: Thu, 4 Dec 2025 18:26:57 +0100
+Subject: [PATCH 37/52] virtio: Fix crash when sriov-pf is set for
+ non-PCI-Express device
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
+RH-Jira: RHEL-186384
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
+RH-Commit: [30/45] 3987bd2c53e0f4cb44d10ad4724a42ccacc4a705 (kmwolf/centos-qemu-kvm)
+
+Setting the sriov-pf property on devices that aren't PCI Express causes
+an assertion failure:
+
+ $ qemu-system-x86_64 \
+ -blockdev null-co,node-name=null \
+ -blockdev null-co,node-name=null2 \
+ -device virtio-blk,drive=null,id=pf \
+ -device virtio-blk,sriov-pf=pf,drive=null2
+ qemu-system-x86_64: ../hw/pci/pcie.c:1062: void pcie_add_capability(PCIDevice *, uint16_t, uint8_t, uint16_t, uint16_t): Assertion `offset >= PCI_CONFIG_SPACE_SIZE' failed.
+
+This is because proxy->last_pcie_cap_offset is only initialised to a
+non-zero value in virtio_pci_realize() if it's a PCI Express device, and
+then virtio_pci_device_plugged() still tries to use it.
+
+To fix this, just skip the SR-IOV code for !pci_is_express(). Then the
+next thing pci_qdev_realize() does is call pcie_sriov_register_device(),
+which returns the appropriate error.
+
+Cc: qemu-stable@nongnu.org
+Fixes: d0c280d3fac6 ('pcie_sriov: Make a PCI device with user-created VF ARI-capable')
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
+Reviewed-by: Michael S. Tsirkin <mst@redhat.com>
+Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
+Message-Id: <20251204172657.174391-1-kwolf@redhat.com>
+(cherry picked from commit 623db856476806124e9ae45fbc39e75012261570)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ hw/virtio/virtio-pci.c | 20 +++++++++++---------
+ 1 file changed, 11 insertions(+), 9 deletions(-)
+
+diff --git a/hw/virtio/virtio-pci.c b/hw/virtio/virtio-pci.c
+index 937e22f08a..044ecb4f41 100644
+--- a/hw/virtio/virtio-pci.c
++++ b/hw/virtio/virtio-pci.c
+@@ -2172,15 +2172,17 @@ static void virtio_pci_device_plugged(DeviceState *d, Error **errp)
+ PCI_BASE_ADDRESS_SPACE_IO, &proxy->bar);
+ }
+
+- if (pci_is_vf(&proxy->pci_dev)) {
+- pcie_ari_init(&proxy->pci_dev, proxy->last_pcie_cap_offset);
+- proxy->last_pcie_cap_offset += PCI_ARI_SIZEOF;
+- } else {
+- res = pcie_sriov_pf_init_from_user_created_vfs(
+- &proxy->pci_dev, proxy->last_pcie_cap_offset, errp);
+- if (res > 0) {
+- proxy->last_pcie_cap_offset += res;
+- virtio_add_feature(&vdev->host_features, VIRTIO_F_SR_IOV);
++ if (pci_is_express(&proxy->pci_dev)) {
++ if (pci_is_vf(&proxy->pci_dev)) {
++ pcie_ari_init(&proxy->pci_dev, proxy->last_pcie_cap_offset);
++ proxy->last_pcie_cap_offset += PCI_ARI_SIZEOF;
++ } else {
++ res = pcie_sriov_pf_init_from_user_created_vfs(
++ &proxy->pci_dev, proxy->last_pcie_cap_offset, errp);
++ if (res > 0) {
++ proxy->last_pcie_cap_offset += res;
++ virtio_add_feature(&vdev->host_features, VIRTIO_F_SR_IOV);
++ }
+ }
+ }
+ }
+--
+2.52.0
+
diff --git a/kvm-virtio-blk-fix-zone-report-buffer-out-of-memory-CVE-.patch b/kvm-virtio-blk-fix-zone-report-buffer-out-of-memory-CVE-.patch
new file mode 100644
index 0000000..42915fb
--- /dev/null
+++ b/kvm-virtio-blk-fix-zone-report-buffer-out-of-memory-CVE-.patch
@@ -0,0 +1,300 @@
+From 488b339be87be50b40851e85aa42aa76adbe1782 Mon Sep 17 00:00:00 2001
+From: Stefan Hajnoczi <stefanha@redhat.com>
+Date: Fri, 10 Apr 2026 08:11:28 -0400
+Subject: [PATCH 40/52] virtio-blk: fix zone report buffer out-of-memory
+ (CVE-2026-5761)
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
+RH-Jira: RHEL-186384
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
+RH-Commit: [33/45] 28556c36d10da652192eb20874ce4b129b0a2b85 (kmwolf/centos-qemu-kvm)
+
+An internal buffer is used when processing VIRTIO_BLK_T_ZONE_REPORT
+requests. The buffer's size is controlled by the guest. A large value
+can result in g_malloc() failure and the QEMU process aborts, resulting
+in a Denial of Service (DoS) (most likely in cases where an untrusted
+guest application or a nested guest with virtio-blk passthrough is able
+to abort QEMU).
+
+Modify the zone report implementation to work incrementally with a
+bounded buffer size.
+
+This is purely a QEMU implementation issue and no VIRTIO spec changes
+are needed.
+
+Mingyuan Luo found this bug and provided a reproducer which I haven't
+put into tests/qtest/ because it requires a zoned storage device (e.g.
+root and modprobe null_blk):
+
+1) Prepare a zoned nullblk backend (/dev/nullb0):
+
+sudo modprobe -r null_blk || true
+sudo modprobe null_blk nr_devices=1 zoned=1
+sudo chmod 0666 /dev/nullb0
+cat /sys/block/nullb0/queue/zoned
+
+2) Create qtest input:
+
+cat >/tmp/vblk-zone-report-oom.qtest <<'EOF'
+outl 0xcf8 0x80002004
+outw 0xcfc 0x0007
+outl 0xcf8 0x80002010
+outl 0xcfc 0x0000c001
+outb 0xc012 0x00
+outb 0xc012 0x01
+outb 0xc012 0x03
+outl 0xc004 0x00000000
+outw 0xc00e 0x0000
+outl 0xc008 0x00000100
+outb 0xc012 0x07
+writel 0x00020000 0x00000010
+writel 0x00020004 0x00000000
+writeq 0x00020008 0x0000000000000000
+writeq 0x00100000 0x0000000000020000
+writel 0x00100008 0x00000010
+writew 0x0010000c 0x0001
+writew 0x0010000e 0x0001
+EOF
+
+for i in $(seq 1 1022); do
+d=$((0x00100000 + i * 16))
+n=$((i + 1))
+printf 'writeq 0x%08x 0x0000000000200000\n' "$d" >> /tmp/vblk-zone-report-oom.qtest
+printf 'writel 0x%08x 0x1fe00000\n' $((d + 8)) >> /tmp/vblk-zone-report-oom.qtest
+printf 'writew 0x%08x 0x0003\n' $((d + 12)) >> /tmp/vblk-zone-report-oom.qtest
+printf 'writew 0x%08x 0x%04x\n' $((d + 14)) "$n" >> /tmp/vblk-zone-report-oom.qtest
+done
+
+d=$((0x00100000 + 1023 * 16))
+printf 'writeq 0x%08x 0x0000000000200000\n' "$d" >> /tmp/vblk-zone-report-oom.qtest
+printf 'writel 0x%08x 0x1fe00000\n' $((d + 8)) >> /tmp/vblk-zone-report-oom.qtest
+printf 'writew 0x%08x 0x0002\n' $((d + 12)) >> /tmp/vblk-zone-report-oom.qtest
+printf 'writew 0x%08x 0x0000\n' $((d + 14)) >> /tmp/vblk-zone-report-oom.qtest
+cat >> /tmp/vblk-zone-report-oom.qtest <<'EOF'
+writew 0x00104000 0x0000
+writew 0x00104002 0x0001
+writew 0x00104004 0x0000
+outw 0xc010 0x0000
+EOF
+
+3) Run the qtest input with ASAN build (compile qemu with --enable-asan):
+
+build/qemu-system-x86_64 -display none \
+-accel qtest -qtest stdio \
+-machine pc -nodefaults -m 512M -monitor none -serial none \
+-blockdev driver=host_device,node-name=disk0,filename=/dev/nullb0 \
+-device virtio-blk-pci-transitional,drive=disk0,addr=04.0,queue-size=1024 \
+< /tmp/vblk-zone-report-oom.qtest
+
+Cc: Sam Li <faithilikerun@gmail.com>
+Cc: Damien Le Moal <dlemoal@kernel.org>
+Cc: Dmitry Fomichev <dmitry.fomichev@wdc.com>
+Fixes: CVE-2026-5761
+Fixes: 4f7366506a9 ("virtio-blk: add zoned storage emulation for zoned devices")
+Reported-by: Mingyuan Luo <myluo24@m.fudan.edu.cn>
+Reviewed-by: Damien Le Moal <dlemoal@kernel.org>
+Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
+(cherry picked from commit 4913ae36f9796c55d434dcbfa6bdb9ebb3e5e4b1)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ hw/block/virtio-blk.c | 100 ++++++++++++++++++++++++++++--------------
+ 1 file changed, 67 insertions(+), 33 deletions(-)
+
+diff --git a/hw/block/virtio-blk.c b/hw/block/virtio-blk.c
+index a0678300b4..019b46fcf7 100644
+--- a/hw/block/virtio-blk.c
++++ b/hw/block/virtio-blk.c
+@@ -38,6 +38,9 @@
+ #include "hw/virtio/virtio-blk-common.h"
+ #include "qemu/coroutine.h"
+
++/* Internal buffer size limit for zone report */
++#define VIRTIO_BLK_MAX_ZONES_PER_BATCH 4096
++
+ static void virtio_blk_ioeventfd_attach(VirtIOBlock *s);
+
+ static void virtio_blk_init_request(VirtIOBlock *s, VirtQueue *vq,
+@@ -457,15 +460,22 @@ err:
+ return err_status;
+ }
+
++typedef struct {
++ unsigned int total_nr_zones; /* max zones to fill in this request */
++ unsigned int nr_zones_done; /* how many zones have been filled in */
++ int64_t iov_offset; /* current byte position in in_iov[] */
++ int64_t offset; /* current zone report disk offset */
++ unsigned int nr_zones; /* for zone report calls */
++ unsigned int zones_per_batch; /* size of zone report buffer */
++ BlockZoneDescriptor *zones; /* zone report buffer */
++} ZoneReportData;
++
+ typedef struct ZoneCmdData {
+ VirtIOBlockReq *req;
+ struct iovec *in_iov;
+ unsigned in_num;
+ union {
+- struct {
+- unsigned int nr_zones;
+- BlockZoneDescriptor *zones;
+- } zone_report_data;
++ ZoneReportData zone_report_data;
+ struct {
+ int64_t offset;
+ } zone_append_data;
+@@ -522,16 +532,15 @@ static bool check_zoned_request(VirtIOBlock *s, int64_t offset, int64_t len,
+ static void virtio_blk_zone_report_complete(void *opaque, int ret)
+ {
+ ZoneCmdData *data = opaque;
++ ZoneReportData *zrd = &data->zone_report_data;
+ VirtIOBlockReq *req = data->req;
+ VirtIODevice *vdev = VIRTIO_DEVICE(req->dev);
+ struct iovec *in_iov = data->in_iov;
+ unsigned in_num = data->in_num;
+- int64_t zrp_size, n, j = 0;
+- int64_t nz = data->zone_report_data.nr_zones;
++ int64_t n;
++ unsigned nz = zrd->nr_zones;
+ int8_t err_status = VIRTIO_BLK_S_OK;
+- struct virtio_blk_zone_report zrp_hdr = (struct virtio_blk_zone_report) {
+- .nr_zones = cpu_to_le64(nz),
+- };
++ struct virtio_blk_zone_report zrp_hdr = {};
+
+ trace_virtio_blk_zone_report_complete(vdev, req, nz, ret);
+ if (ret) {
+@@ -539,28 +548,18 @@ static void virtio_blk_zone_report_complete(void *opaque, int ret)
+ goto out;
+ }
+
+- zrp_size = sizeof(struct virtio_blk_zone_report)
+- + sizeof(struct virtio_blk_zone_descriptor) * nz;
+- n = iov_from_buf(in_iov, in_num, 0, &zrp_hdr, sizeof(zrp_hdr));
+- if (n != sizeof(zrp_hdr)) {
+- virtio_error(vdev, "Driver provided input buffer that is too small!");
+- err_status = VIRTIO_BLK_S_ZONE_INVALID_CMD;
+- goto out;
+- }
+-
+- for (size_t i = sizeof(zrp_hdr); i < zrp_size;
+- i += sizeof(struct virtio_blk_zone_descriptor), ++j) {
++ for (unsigned j = 0; j < nz; j++) {
+ struct virtio_blk_zone_descriptor desc =
+ (struct virtio_blk_zone_descriptor) {
+- .z_start = cpu_to_le64(data->zone_report_data.zones[j].start
++ .z_start = cpu_to_le64(zrd->zones[j].start
+ >> BDRV_SECTOR_BITS),
+- .z_cap = cpu_to_le64(data->zone_report_data.zones[j].cap
++ .z_cap = cpu_to_le64(zrd->zones[j].cap
+ >> BDRV_SECTOR_BITS),
+- .z_wp = cpu_to_le64(data->zone_report_data.zones[j].wp
++ .z_wp = cpu_to_le64(zrd->zones[j].wp
+ >> BDRV_SECTOR_BITS),
+ };
+
+- switch (data->zone_report_data.zones[j].type) {
++ switch (zrd->zones[j].type) {
+ case BLK_ZT_CONV:
+ desc.z_type = VIRTIO_BLK_ZT_CONV;
+ break;
+@@ -574,7 +573,7 @@ static void virtio_blk_zone_report_complete(void *opaque, int ret)
+ g_assert_not_reached();
+ }
+
+- switch (data->zone_report_data.zones[j].state) {
++ switch (zrd->zones[j].state) {
+ case BLK_ZS_RDONLY:
+ desc.z_state = VIRTIO_BLK_ZS_RDONLY;
+ break;
+@@ -604,18 +603,47 @@ static void virtio_blk_zone_report_complete(void *opaque, int ret)
+ }
+
+ /* TODO: it takes O(n^2) time complexity. Optimizations required. */
+- n = iov_from_buf(in_iov, in_num, i, &desc, sizeof(desc));
++ n = iov_from_buf(in_iov, in_num, zrd->iov_offset, &desc, sizeof(desc));
+ if (n != sizeof(desc)) {
+ virtio_error(vdev, "Driver provided input buffer "
+ "for descriptors that is too small!");
+ err_status = VIRTIO_BLK_S_ZONE_INVALID_CMD;
++ goto out;
+ }
++
++ zrd->iov_offset += sizeof(desc);
++ }
++
++ if (nz > 0) {
++ BlockZoneDescriptor *zone = &zrd->zones[nz - 1];
++ zrd->offset = zone->start + zone->length;
++ }
++
++ zrd->nr_zones_done += nz;
++
++ /* Call zone report again if the end hasn't been reached yet */
++ if (nz == zrd->zones_per_batch &&
++ zrd->nr_zones_done < zrd->total_nr_zones) {
++ zrd->nr_zones = MIN(zrd->zones_per_batch,
++ zrd->total_nr_zones - zrd->nr_zones_done);
++ blk_aio_zone_report(req->dev->blk, zrd->offset, &zrd->nr_zones,
++ zrd->zones, virtio_blk_zone_report_complete, data);
++ return;
++ }
++
++ /* Fill in header now that all zones have been reported */
++ zrp_hdr.nr_zones = cpu_to_le64(zrd->nr_zones_done);
++ n = iov_from_buf(in_iov, in_num, 0, &zrp_hdr, sizeof(zrp_hdr));
++ if (n != sizeof(zrp_hdr)) {
++ virtio_error(vdev, "Driver provided input buffer that is too small!");
++ err_status = VIRTIO_BLK_S_ZONE_INVALID_CMD;
++ goto out;
+ }
+
+ out:
+ virtio_blk_req_complete(req, err_status);
+ g_free(req);
+- g_free(data->zone_report_data.zones);
++ g_free(zrd->zones);
+ g_free(data);
+ }
+
+@@ -627,7 +655,8 @@ static void virtio_blk_handle_zone_report(VirtIOBlockReq *req,
+ VirtIODevice *vdev = VIRTIO_DEVICE(s);
+ unsigned int nr_zones;
+ ZoneCmdData *data;
+- int64_t zone_size, offset;
++ ZoneReportData *zrd;
++ int64_t offset;
+ uint8_t err_status;
+
+ if (req->in_len < sizeof(struct virtio_blk_inhdr) +
+@@ -649,16 +678,21 @@ static void virtio_blk_handle_zone_report(VirtIOBlockReq *req,
+ trace_virtio_blk_handle_zone_report(vdev, req,
+ offset >> BDRV_SECTOR_BITS, nr_zones);
+
+- zone_size = sizeof(BlockZoneDescriptor) * nr_zones;
+ data = g_malloc(sizeof(ZoneCmdData));
+ data->req = req;
+ data->in_iov = in_iov;
+ data->in_num = in_num;
+- data->zone_report_data.nr_zones = nr_zones;
+- data->zone_report_data.zones = g_malloc(zone_size),
+
+- blk_aio_zone_report(s->blk, offset, &data->zone_report_data.nr_zones,
+- data->zone_report_data.zones,
++ zrd = &data->zone_report_data;
++ zrd->total_nr_zones = nr_zones;
++ zrd->nr_zones_done = 0;
++ zrd->iov_offset = sizeof(struct virtio_blk_zone_report);
++ zrd->offset = offset;
++ zrd->zones_per_batch = MIN(nr_zones, VIRTIO_BLK_MAX_ZONES_PER_BATCH);
++ zrd->zones = g_malloc(zrd->zones_per_batch * sizeof(BlockZoneDescriptor));
++
++ zrd->nr_zones = zrd->zones_per_batch;
++ blk_aio_zone_report(s->blk, offset, &zrd->nr_zones, zrd->zones,
+ virtio_blk_zone_report_complete, data);
+ return;
+ out:
+--
+2.52.0
+
diff --git a/kvm-virtio-scsi-pass-the-same-cdb_size-to-virtio_scsi_po.patch b/kvm-virtio-scsi-pass-the-same-cdb_size-to-virtio_scsi_po.patch
new file mode 100644
index 0000000..fad2908
--- /dev/null
+++ b/kvm-virtio-scsi-pass-the-same-cdb_size-to-virtio_scsi_po.patch
@@ -0,0 +1,131 @@
+From c1a573c922a2c99060dfb63413cd47db2c784ca6 Mon Sep 17 00:00:00 2001
+From: Paolo Bonzini <pbonzini@redhat.com>
+Date: Fri, 27 Mar 2026 22:00:09 +0100
+Subject: [PATCH 38/52] virtio-scsi: pass the same cdb_size to
+ virtio_scsi_pop_req and virtio_scsi_handle_cmd_req_prepare
+
+RH-Author: Kevin Wolf <kwolf@redhat.com>
+RH-MergeRequest: 504: virt-storage: Backport stable branch fixes
+RH-Jira: RHEL-186384
+RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
+RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
+RH-Commit: [31/45] 6fe89d004c1a32f220b7104f5f7b2d9ee116d5f1 (kmwolf/centos-qemu-kvm)
+
+Ensure that there is no allocation/usage mismatch when requests
+are processed in virtio_scsi_handle_cmd_vq. To do this,
+retrieve the value once and pass it to both functions.
+
+For other calls to virtio_scsi_pop_req the extra size
+can be 0, because control and event requests fit
+entirely in VirtIOSCSIReq.
+
+Reported-by: Jihe Wang <wangjihe.mail@gmail.com>
+Tested-by: Jihe Wang <wangjihe.mail@gmail.com>
+Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
+Fixes: CVE-2026-5763
+Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
+(cherry picked from commit 79971302935472232a68073faddb085177e3ca54)
+Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+---
+ hw/scsi/virtio-scsi.c | 26 +++++++++++++++-----------
+ 1 file changed, 15 insertions(+), 11 deletions(-)
+
+diff --git a/hw/scsi/virtio-scsi.c b/hw/scsi/virtio-scsi.c
+index 34ae14f7bf..0ce4718407 100644
+--- a/hw/scsi/virtio-scsi.c
++++ b/hw/scsi/virtio-scsi.c
+@@ -231,16 +231,16 @@ static int virtio_scsi_parse_req(VirtIOSCSIReq *req,
+ return 0;
+ }
+
+-static VirtIOSCSIReq *virtio_scsi_pop_req(VirtIOSCSI *s, VirtQueue *vq, QemuMutex *vq_lock)
++static VirtIOSCSIReq *virtio_scsi_pop_req(VirtIOSCSI *s, VirtQueue *vq, size_t extra_req_size,
++ QemuMutex *vq_lock)
+ {
+- VirtIOSCSICommon *vs = (VirtIOSCSICommon *)s;
+ VirtIOSCSIReq *req;
+
+ if (vq_lock) {
+ qemu_mutex_lock(vq_lock);
+ }
+
+- req = virtqueue_pop(vq, sizeof(VirtIOSCSIReq) + vs->cdb_size);
++ req = virtqueue_pop(vq, sizeof(VirtIOSCSIReq) + extra_req_size);
+
+ if (vq_lock) {
+ qemu_mutex_unlock(vq_lock);
+@@ -674,7 +674,7 @@ static void virtio_scsi_handle_ctrl_vq(VirtIOSCSI *s, VirtQueue *vq)
+ {
+ VirtIOSCSIReq *req;
+
+- while ((req = virtio_scsi_pop_req(s, vq, &s->ctrl_lock))) {
++ while ((req = virtio_scsi_pop_req(s, vq, 0, &s->ctrl_lock))) {
+ virtio_scsi_handle_ctrl_req(s, req);
+ }
+ }
+@@ -842,13 +842,14 @@ static void virtio_scsi_fail_cmd_req(VirtIOSCSIReq *req)
+ virtio_scsi_complete_cmd_req(req);
+ }
+
+-static int virtio_scsi_handle_cmd_req_prepare(VirtIOSCSI *s, VirtIOSCSIReq *req)
++static int virtio_scsi_handle_cmd_req_prepare(VirtIOSCSI *s, VirtIOSCSIReq *req,
++ size_t cdb_size)
+ {
+ VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(s);
+ SCSIDevice *d;
+ int rc;
+
+- rc = virtio_scsi_parse_req(req, sizeof(VirtIOSCSICmdReq) + vs->cdb_size,
++ rc = virtio_scsi_parse_req(req, sizeof(VirtIOSCSICmdReq) + cdb_size,
+ sizeof(VirtIOSCSICmdResp) + vs->sense_size);
+ if (rc < 0) {
+ if (rc == -ENOTSUP) {
+@@ -870,7 +871,7 @@ static int virtio_scsi_handle_cmd_req_prepare(VirtIOSCSI *s, VirtIOSCSIReq *req)
+ }
+ req->sreq = scsi_req_new(d, req->req.cmd.tag,
+ virtio_scsi_get_lun(req->req.cmd.lun),
+- req->req.cmd.cdb, vs->cdb_size, req);
++ req->req.cmd.cdb, cdb_size, req);
+
+ if (req->sreq->cmd.mode != SCSI_XFER_NONE
+ && (req->sreq->cmd.mode != req->mode ||
+@@ -905,12 +906,15 @@ static void virtio_scsi_handle_cmd_vq(VirtIOSCSI *s, VirtQueue *vq)
+ QTAILQ_HEAD(, VirtIOSCSIReq) reqs = QTAILQ_HEAD_INITIALIZER(reqs);
+
+ do {
++ VirtIOSCSICommon *vs = (VirtIOSCSICommon *)s;
++ size_t cdb_size = qatomic_read(&vs->cdb_size);
++
+ if (suppress_notifications) {
+ virtio_queue_set_notification(vq, 0);
+ }
+
+- while ((req = virtio_scsi_pop_req(s, vq, NULL))) {
+- ret = virtio_scsi_handle_cmd_req_prepare(s, req);
++ while ((req = virtio_scsi_pop_req(s, vq, cdb_size, NULL))) {
++ ret = virtio_scsi_handle_cmd_req_prepare(s, req, cdb_size);
+ if (!ret) {
+ QTAILQ_INSERT_TAIL(&reqs, req, next);
+ } else if (ret == -EINVAL) {
+@@ -981,7 +985,7 @@ static void virtio_scsi_set_config(VirtIODevice *vdev,
+ }
+
+ vs->sense_size = virtio_ldl_p(vdev, &scsiconf->sense_size);
+- vs->cdb_size = virtio_ldl_p(vdev, &scsiconf->cdb_size);
++ qatomic_set(&vs->cdb_size, virtio_ldl_p(vdev, &scsiconf->cdb_size));
+ }
+
+ static uint64_t virtio_scsi_get_features(VirtIODevice *vdev,
+@@ -1042,7 +1046,7 @@ static void virtio_scsi_push_event(VirtIOSCSI *s,
+ return;
+ }
+
+- req = virtio_scsi_pop_req(s, vs->event_vq, &s->event_lock);
++ req = virtio_scsi_pop_req(s, vs->event_vq, 0, &s->event_lock);
+ WITH_QEMU_LOCK_GUARD(&s->event_lock) {
+ if (!req) {
+ s->events_dropped = true;
+--
+2.52.0
+
diff --git a/qemu.spec b/qemu.spec
index bb4fbbf..d5c58a4 100644
--- a/qemu.spec
+++ b/qemu.spec
@@ -143,7 +143,7 @@ Obsoletes: %{name}-block-ssh <= %{epoch}:%{version} \
Summary: QEMU is a machine emulator and virtualizer
Name: qemu-kvm
Version: 10.1.0
-Release: 21%{?rcrel}%{?dist}%{?cc_suffix}
+Release: 22%{?rcrel}%{?dist}%{?cc_suffix}
# 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)
@@ -705,6 +705,110 @@ Patch272: kvm-vfio-Add-Error-parameter-to-vfio_region_setup.patch
Patch273: kvm-hw-vfio-align-mmap-to-power-of-2-of-region-size-for-.patch
# For RHEL-184530 - CVE-2026-48914 qemu-kvm: Heap buffer overflow in virtio-blk SCSI request handling [rhel-10.3]
Patch274: kvm-virtio-blk-add-missing-VIRTIO_BLK_T_SCSI_CMD-size-ch.patch
+# For RHEL-121686 - qemu-kvm hung during drain after double pause
+Patch275: kvm-blkdebug-Add-delay-ns-option.patch
+# For RHEL-121686 - qemu-kvm hung during drain after double pause
+Patch276: kvm-block-Add-blk_co_start-end_request-and-BDRV_REQ_NO_Q.patch
+# For RHEL-121686 - qemu-kvm hung during drain after double pause
+Patch277: kvm-block-Add-flags-parameter-to-blk_-_pdiscard.patch
+# For RHEL-121686 - qemu-kvm hung during drain after double pause
+Patch278: kvm-ide-Minimal-fix-for-deadlock-between-TRIM-and-drain.patch
+# For RHEL-121686 - qemu-kvm hung during drain after double pause
+Patch279: kvm-ide-Clean-up-ide_trim_co_entry-to-be-idiomatic-corou.patch
+# For RHEL-121686 - qemu-kvm hung during drain after double pause
+Patch280: kvm-ide-test-Factor-out-wait_dma_completion.patch
+# For RHEL-121686 - qemu-kvm hung during drain after double pause
+Patch281: kvm-ide-test-Test-reset-during-TRIM.patch
+# For RHEL-186384 - virt-storage: Backport stable branch fixes
+Patch282: kvm-block-graph-lock-fix-missed-wakeup-in-bdrv_graph_co_.patch
+# For RHEL-186384 - virt-storage: Backport stable branch fixes
+Patch283: kvm-block-curl-fix-curl-internal-handles-handling.patch
+# For RHEL-186384 - virt-storage: Backport stable branch fixes
+Patch284: kvm-block-curl.c-Use-explicit-long-constants-in-curl_eas.patch
+# For RHEL-186384 - virt-storage: Backport stable branch fixes
+Patch285: kvm-block-curl.c-Fix-CURLOPT_VERBOSE-parameter-type.patch
+# For RHEL-186384 - virt-storage: Backport stable branch fixes
+Patch286: kvm-block-curl-fix-concurrent-completion-handling.patch
+# For RHEL-186384 - virt-storage: Backport stable branch fixes
+Patch287: kvm-block-curl-free-s-password-in-cleanup-paths.patch
+# For RHEL-186384 - virt-storage: Backport stable branch fixes
+Patch288: kvm-nvme-Kick-and-check-completions-in-BDS-context.patch
+# For RHEL-186384 - virt-storage: Backport stable branch fixes
+Patch289: kvm-nvme-Note-in-which-AioContext-some-functions-run.patch
+# For RHEL-186384 - virt-storage: Backport stable branch fixes
+Patch290: kvm-block-remove-detached-header-option-from-opts-after-.patch
+# For RHEL-186384 - virt-storage: Backport stable branch fixes
+Patch291: kvm-block-fix-luks-amend-when-run-in-coroutine.patch
+# For RHEL-186384 - virt-storage: Backport stable branch fixes
+Patch292: kvm-qed-Don-t-try-to-flush-during-incoming-migration.patch
+# For RHEL-186384 - virt-storage: Backport stable branch fixes
+Patch293: kvm-block-vmdk-fix-OOB-read-in-vmdk_read_extent.patch
+# For RHEL-186384 - virt-storage: Backport stable branch fixes
+Patch294: kvm-block-throttle-groups-fix-deadlock-with-iolimits-and.patch
+# For RHEL-186384 - virt-storage: Backport stable branch fixes
+Patch295: kvm-throttle-group-Fix-race-condition-in-throttle_group_.patch
+# For RHEL-186384 - virt-storage: Backport stable branch fixes
+Patch296: kvm-qemu-img-Fix-amend-option-parse-error-handling.patch
+# For RHEL-186384 - virt-storage: Backport stable branch fixes
+Patch297: kvm-qemu-img-rebase-don-t-exceed-IO_BUF_SIZE-in-one-oper.patch
+# For RHEL-186384 - virt-storage: Backport stable branch fixes
+Patch298: kvm-python-backport-drop-Python3.6-workarounds.patch
+# For RHEL-186384 - virt-storage: Backport stable branch fixes
+Patch299: kvm-python-backport-Remove-deprecated-get_event_loop-cal.patch
+# For RHEL-186384 - virt-storage: Backport stable branch fixes
+Patch300: kvm-python-backport-avoid-creating-additional-event-loop.patch
+# For RHEL-186384 - virt-storage: Backport stable branch fixes
+Patch301: kvm-iotests-147-ensure-temporary-sockets-are-closed-befo.patch
+# For RHEL-186384 - virt-storage: Backport stable branch fixes
+Patch302: kvm-iotests-151-ensure-subprocesses-are-cleaned-up.patch
+# For RHEL-186384 - virt-storage: Backport stable branch fixes
+Patch303: kvm-tests-qemu-iotest-fix-iotest-024-with-qed-images.patch
+# For RHEL-186384 - virt-storage: Backport stable branch fixes
+Patch304: kvm-tests-qemu-iotests-Fix-check-for-existing-file-in-_r.patch
+# For RHEL-186384 - virt-storage: Backport stable branch fixes
+Patch305: kvm-async-access-bottom-half-flags-with-qatomic_read.patch
+# For RHEL-186384 - virt-storage: Backport stable branch fixes
+Patch306: kvm-block-linux-aio-bound-ioq_submit-recursion-depth.patch
+# For RHEL-186384 - virt-storage: Backport stable branch fixes
+Patch307: kvm-block-io-fallback-to-bounce-buffer-if-BLKZEROOUT-is-.patch
+# For RHEL-186384 - virt-storage: Backport stable branch fixes
+Patch308: kvm-file-posix-populate-pwrite_zeroes_alignment.patch
+# For RHEL-186384 - virt-storage: Backport stable branch fixes
+Patch309: kvm-block-use-pwrite_zeroes_alignment-when-writing-first.patch
+# For RHEL-186384 - virt-storage: Backport stable branch fixes
+Patch310: kvm-iotests-add-Linux-loop-device-image-creation-test.patch
+# For RHEL-186384 - virt-storage: Backport stable branch fixes
+Patch311: kvm-virtio-Fix-crash-when-sriov-pf-is-set-for-non-PCI-Ex.patch
+# For RHEL-186384 - virt-storage: Backport stable branch fixes
+Patch312: kvm-virtio-scsi-pass-the-same-cdb_size-to-virtio_scsi_po.patch
+# For RHEL-186384 - virt-storage: Backport stable branch fixes
+Patch313: kvm-hw-scsi-avoid-deadlock-upon-TMF-request-cancelling-w.patch
+# For RHEL-186384 - virt-storage: Backport stable branch fixes
+Patch314: kvm-virtio-blk-fix-zone-report-buffer-out-of-memory-CVE-.patch
+# For RHEL-186384 - virt-storage: Backport stable branch fixes
+Patch315: kvm-ide-Fix-potential-assertion-failure-on-VM-stop-for-P.patch
+# For RHEL-186384 - virt-storage: Backport stable branch fixes
+Patch316: kvm-block-Create-DEFAULT_BLOCK_CONF-macro.patch
+# For RHEL-186384 - virt-storage: Backport stable branch fixes
+Patch317: kvm-block-Add-more-defaults-to-DEFAULT_BLOCK_CONF.patch
+# For RHEL-186384 - virt-storage: Backport stable branch fixes
+Patch318: kvm-block-mirror-check-range-when-setting-zero-bitmap-fo.patch
+# For RHEL-186384 - virt-storage: Backport stable branch fixes
+Patch319: kvm-iotests-test-active-mirror-with-unaligned-small-writ.patch
+# For RHEL-186384 - virt-storage: Backport stable branch fixes
+Patch320: kvm-block-mirror-fix-assertion-failure-upon-duplicate-co.patch
+# For RHEL-186384 - virt-storage: Backport stable branch fixes
+Patch321: kvm-commit-Drain-nodes-across-all-of-bdrv_commit.patch
+# For RHEL-186384 - virt-storage: Backport stable branch fixes
+Patch322: kvm-qemu-io-Add-aio_discard-command.patch
+# For RHEL-186384 - virt-storage: Backport stable branch fixes
+Patch323: kvm-qcow2-Fix-corruption-on-discard-during-write-with-CO.patch
+# For RHEL-186384 - virt-storage: Backport stable branch fixes
+Patch324: kvm-iotests-046-Test-that-discard-write_zeroes-wait-for-.patch
+# For RHEL-186384 - virt-storage: Backport stable branch fixes
+Patch325: kvm-qcow2-Fix-data-loss-on-zero-write-with-detect-zeroes.patch
+# For RHEL-186384 - virt-storage: Backport stable branch fixes
+Patch326: kvm-block-Fix-crash-after-setting-latency-historygram-wi.patch
%if %{have_clang}
BuildRequires: clang
@@ -1784,6 +1888,64 @@ useradd -r -u 107 -g qemu -G kvm -d / -s /sbin/nologin \
%endif
%changelog
+* Tue Jun 23 2026 Miroslav Rezanina <mrezanin@redhat.com> - 10.1.0-22
+- kvm-blkdebug-Add-delay-ns-option.patch [RHEL-121686]
+- kvm-block-Add-blk_co_start-end_request-and-BDRV_REQ_NO_Q.patch [RHEL-121686]
+- kvm-block-Add-flags-parameter-to-blk_-_pdiscard.patch [RHEL-121686]
+- kvm-ide-Minimal-fix-for-deadlock-between-TRIM-and-drain.patch [RHEL-121686]
+- kvm-ide-Clean-up-ide_trim_co_entry-to-be-idiomatic-corou.patch [RHEL-121686]
+- kvm-ide-test-Factor-out-wait_dma_completion.patch [RHEL-121686]
+- kvm-ide-test-Test-reset-during-TRIM.patch [RHEL-121686]
+- kvm-block-graph-lock-fix-missed-wakeup-in-bdrv_graph_co_.patch [RHEL-186384]
+- kvm-block-curl-fix-curl-internal-handles-handling.patch [RHEL-186384]
+- kvm-block-curl.c-Use-explicit-long-constants-in-curl_eas.patch [RHEL-186384]
+- kvm-block-curl.c-Fix-CURLOPT_VERBOSE-parameter-type.patch [RHEL-186384]
+- kvm-block-curl-fix-concurrent-completion-handling.patch [RHEL-186384]
+- kvm-block-curl-free-s-password-in-cleanup-paths.patch [RHEL-186384]
+- kvm-nvme-Kick-and-check-completions-in-BDS-context.patch [RHEL-186384]
+- kvm-nvme-Note-in-which-AioContext-some-functions-run.patch [RHEL-186384]
+- kvm-block-remove-detached-header-option-from-opts-after-.patch [RHEL-186384]
+- kvm-block-fix-luks-amend-when-run-in-coroutine.patch [RHEL-186384]
+- kvm-qed-Don-t-try-to-flush-during-incoming-migration.patch [RHEL-186384]
+- kvm-block-vmdk-fix-OOB-read-in-vmdk_read_extent.patch [RHEL-186384]
+- kvm-block-throttle-groups-fix-deadlock-with-iolimits-and.patch [RHEL-186384]
+- kvm-throttle-group-Fix-race-condition-in-throttle_group_.patch [RHEL-186384]
+- kvm-qemu-img-Fix-amend-option-parse-error-handling.patch [RHEL-186384]
+- kvm-qemu-img-rebase-don-t-exceed-IO_BUF_SIZE-in-one-oper.patch [RHEL-186384]
+- kvm-python-backport-drop-Python3.6-workarounds.patch [RHEL-186384]
+- kvm-python-backport-Remove-deprecated-get_event_loop-cal.patch [RHEL-186384]
+- kvm-python-backport-avoid-creating-additional-event-loop.patch [RHEL-186384]
+- kvm-iotests-147-ensure-temporary-sockets-are-closed-befo.patch [RHEL-186384]
+- kvm-iotests-151-ensure-subprocesses-are-cleaned-up.patch [RHEL-186384]
+- kvm-tests-qemu-iotest-fix-iotest-024-with-qed-images.patch [RHEL-186384]
+- kvm-tests-qemu-iotests-Fix-check-for-existing-file-in-_r.patch [RHEL-186384]
+- kvm-async-access-bottom-half-flags-with-qatomic_read.patch [RHEL-186384]
+- kvm-block-linux-aio-bound-ioq_submit-recursion-depth.patch [RHEL-186384]
+- kvm-block-io-fallback-to-bounce-buffer-if-BLKZEROOUT-is-.patch [RHEL-186384]
+- kvm-file-posix-populate-pwrite_zeroes_alignment.patch [RHEL-186384]
+- kvm-block-use-pwrite_zeroes_alignment-when-writing-first.patch [RHEL-186384]
+- kvm-iotests-add-Linux-loop-device-image-creation-test.patch [RHEL-186384]
+- kvm-virtio-Fix-crash-when-sriov-pf-is-set-for-non-PCI-Ex.patch [RHEL-186384]
+- kvm-virtio-scsi-pass-the-same-cdb_size-to-virtio_scsi_po.patch [RHEL-186384]
+- kvm-hw-scsi-avoid-deadlock-upon-TMF-request-cancelling-w.patch [RHEL-186384]
+- kvm-virtio-blk-fix-zone-report-buffer-out-of-memory-CVE-.patch [RHEL-186384]
+- kvm-ide-Fix-potential-assertion-failure-on-VM-stop-for-P.patch [RHEL-186384]
+- kvm-block-Create-DEFAULT_BLOCK_CONF-macro.patch [RHEL-186384]
+- kvm-block-Add-more-defaults-to-DEFAULT_BLOCK_CONF.patch [RHEL-186384]
+- kvm-block-mirror-check-range-when-setting-zero-bitmap-fo.patch [RHEL-186384]
+- kvm-iotests-test-active-mirror-with-unaligned-small-writ.patch [RHEL-186384]
+- kvm-block-mirror-fix-assertion-failure-upon-duplicate-co.patch [RHEL-186384]
+- kvm-commit-Drain-nodes-across-all-of-bdrv_commit.patch [RHEL-186384]
+- kvm-qemu-io-Add-aio_discard-command.patch [RHEL-186384]
+- kvm-qcow2-Fix-corruption-on-discard-during-write-with-CO.patch [RHEL-186384]
+- kvm-iotests-046-Test-that-discard-write_zeroes-wait-for-.patch [RHEL-186384]
+- kvm-qcow2-Fix-data-loss-on-zero-write-with-detect-zeroes.patch [RHEL-186384]
+- kvm-block-Fix-crash-after-setting-latency-historygram-wi.patch [RHEL-186384]
+- Resolves: RHEL-121686
+ (qemu-kvm hung during drain after double pause)
+- Resolves: RHEL-186384
+ (virt-storage: Backport stable branch fixes)
+
* Fri Jun 19 2026 Miroslav Rezanina <mrezanin@redhat.com> - 10.1.0-21
- kvm-hw-vfio-sort-and-validate-sparse-mmap-regions-by-off.patch [RHEL-150900]
- kvm-vfio-Add-Error-parameter-to-vfio_region_setup.patch [RHEL-150900]
^ permalink raw reply related [flat|nested] only message in thread
only message in thread, other threads:[~2026-06-30 15:09 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-06-30 15:09 [rpms/qemu] eln: * Tue Jun 23 2026 Miroslav Rezanina <mrezanin@redhat.com> - 10.1.0-22 Miroslav Rezanina
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox