From: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
Subject: [Devel] [PATCH VZ10 06/10] drivers/md/dm-qcow2: update metadata on subclusters discard
Date: Wed, 12 Aug 2026 21:56:34 +0300 [thread overview]
Message-ID: <20260812185638.110779-7-andrey.zhadchenko@virtuozzo.com> (raw)
In-Reply-To: <20260812185638.110779-1-andrey.zhadchenko@virtuozzo.com>
On images with extended L2 entries a discard smaller than the whole
cluster only punches a hole in the image file, while the subcluster
allocation bitmap still reports the range as allocated.
Extend prepare_cluster_discard() to also handle discards covering
whole subclusters of a mapped non-compressed cluster: clear the
"allocated" bits of the covered subclusters in the extended L2 entry
via the existing only_set_ext_l2 machinery. If the range may be
mapped in lower delta, set the "reads as zeroes" bits instead, so the
discarded range doesn't expose stale lower data. The cluster itself
remains allocated, so refcounts are not touched and the unuse stage
has nothing to do (empty unuse range). The covered range is hole
punched unless the cluster is shared with a snapshot.
ext_l2 and unuse range handling are in different if blocks for a while:
next patch will cover the case when we need uncoupling.
Feature: dm-qcow2: block device over QCOW2 files driver
https://virtuozzo.atlassian.net/browse/VSTOR-139406
Signed-off-by: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
---
drivers/md/dm-qcow2-map.c | 61 +++++++++++++++++++++++++++++++--------
1 file changed, 49 insertions(+), 12 deletions(-)
diff --git a/drivers/md/dm-qcow2-map.c b/drivers/md/dm-qcow2-map.c
index 3e3d10c3ebec4..bc50af2ec205b 100644
--- a/drivers/md/dm-qcow2-map.c
+++ b/drivers/md/dm-qcow2-map.c
@@ -107,6 +107,21 @@ static bool qio_covers_full_clu(struct qcow2 *qcow2, struct qio *qio)
qio->bi_iter.bi_size == qcow2->clu_size;
}
+/* Mask of subclusters fully covered by qio (qio is trimmed inward) */
+static u32 qio_full_subclus_mask(struct qcow2 *qcow2, struct qio *qio)
+{
+ u32 off = bytes_off_in_cluster(qcow2, qio);
+ u32 first_bit = DIV_ROUND_UP(off, qcow2->subclu_size);
+ u32 end_bit = (off + qio->bi_iter.bi_size) / qcow2->subclu_size;
+
+ WARN_ON_ONCE(!qcow2->ext_l2);
+
+ if (end_bit <= first_bit)
+ return 0;
+
+ return GENMASK(end_bit - 1, first_bit);
+}
+
static loff_t compressed_clu_end_pos(loff_t start, sector_t compressed_sectors)
{
if (start % SECTOR_SIZE == 0)
@@ -3361,7 +3376,8 @@ static bool qio_discard_updates_metadata(struct qcow2 *qcow2, struct qio *qio,
return false;
if (qio_covers_full_clu(qcow2, qio))
return true;
- return false;
+ return qcow2->ext_l2 && !map->compressed &&
+ ((u32)map->ext_l2 & qio_full_subclus_mask(qcow2, qio));
}
/*
@@ -3371,6 +3387,9 @@ static bool qio_discard_updates_metadata(struct qcow2 *qcow2, struct qio *qio,
*
* Discard covering the whole cluster replaces the entry and unuses
* the discarded (or COW source) cluster.
+ * Subclusters fully covered by the discard are marked unallocated in
+ * ext_l2 bitmap: the cluster itself remains allocated, so refcounts
+ * are not touched (hence empty unuse range).
* If the backing is present, set 'reads as zeroes' to avoid exposing
* stale data.
*/
@@ -3380,8 +3399,9 @@ static int prepare_cluster_discard(struct qcow2 *qcow2, struct qio **qio,
bool zeroes = maybe_mapped_in_lower_delta(qcow2, *qio);
u32 index_in_page = map->l2.index_in_page;
struct md_page *md = map->l2.md;
- u64 new_ext_l2 = map->ext_l2;
loff_t unuse_pos, unuse_end;
+ bool whole_clu;
+ u64 new_ext_l2 = 0;
struct qio_ext *ext;
int ret;
@@ -3397,17 +3417,32 @@ static int prepare_cluster_discard(struct qcow2 *qcow2, struct qio **qio,
}
spin_unlock_irq(&qcow2->md_pages_lock);
- if (map->clu_is_cow) {
- /* Cluster is shared or compressed. Decrement refcount. */
- unuse_pos = map->cow_clu_pos;
- unuse_end = map->cow_clu_end;
+ whole_clu = qio_covers_full_clu(qcow2, *qio);
+ if (whole_clu) {
+ if (zeroes && qcow2->ext_l2)
+ new_ext_l2 = (u64)U32_MAX << 32;
+ } else {
+ u64 mask = qio_full_subclus_mask(qcow2, *qio);
+
+ new_ext_l2 = map->ext_l2 & ~(mask << 32 | mask);
+ if (zeroes)
+ new_ext_l2 |= mask << 32;
+ }
+
+ if (whole_clu) {
+ if (map->clu_is_cow) {
+ /* Cluster is shared or compressed. Decrement refcount. */
+ unuse_pos = map->cow_clu_pos;
+ unuse_end = map->cow_clu_end;
+ } else {
+ /* Nobody else refers the cluster: unuse it */
+ unuse_pos = map->data_clu_pos;
+ unuse_end = map->data_clu_pos + qcow2->clu_size;
+ }
} else {
- /* Nobody else refers the cluster: unuse it after discard */
- unuse_pos = map->data_clu_pos;
- unuse_end = map->data_clu_pos + qcow2->clu_size;
+ /* Subclusters become unallocated, cluster remains */
+ unuse_pos = unuse_end = 0;
}
- if (zeroes && qcow2->ext_l2)
- new_ext_l2 = (u64)U32_MAX << 32;
ret = prepare_l_entry_replace(qcow2, map, *qio, md, index_in_page,
unuse_pos, unuse_end, L2_LEVEL);
@@ -3418,7 +3453,9 @@ static int prepare_cluster_discard(struct qcow2 *qcow2, struct qio **qio,
ext->lx_md = md;
ext->new_ext_l2 = new_ext_l2;
- if (zeroes && !qcow2->ext_l2)
+ if (!whole_clu)
+ ext->only_set_ext_l2 = true;
+ else if (zeroes && !qcow2->ext_l2)
ext->set_all_zeroes = true;
(*qio)->flags |= QIO_IS_DISCARD_FL;
--
2.43.5
next prev parent reply other threads:[~2026-08-12 18:56 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-12 18:56 [Devel] [PATCH VZ10 00/10] dm-qcow2: improve discard and read-only merge handling Andrey Zhadchenko
2026-08-12 18:56 ` [Devel] [PATCH VZ10 01/10] drivers/md/dm-qcow2: fix revert_cluster_alloc() for ext_l2 case Andrey Zhadchenko
2026-08-12 18:56 ` [Devel] [PATCH VZ10 02/10] drivers/md/dm-qcow2: generalize COW index update machinery Andrey Zhadchenko
2026-08-12 18:56 ` [Devel] [PATCH VZ10 03/10] drivers/md/dm-qcow2: update metadata on whole cluster discard Andrey Zhadchenko
2026-08-12 18:56 ` [Devel] [PATCH VZ10 04/10] drivers/md/dm-qcow2: never trigger COW or allocation on discard Andrey Zhadchenko
2026-08-12 18:56 ` [Devel] [PATCH VZ10 05/10] drivers/md/dm-qcow2: set L2_READS_ALL_ZEROES after some discards Andrey Zhadchenko
2026-08-12 18:56 ` Andrey Zhadchenko [this message]
2026-08-12 18:56 ` [Devel] [PATCH VZ10 07/10] drivers/md/dm-qcow2: unmap cluster when discard clears last allocated subclusters Andrey Zhadchenko
2026-08-12 18:56 ` [Devel] [PATCH VZ10 08/10] drivers/md/dm-qcow2: do discards during backward merge only for writable image Andrey Zhadchenko
2026-08-12 18:56 ` [Devel] [PATCH VZ10 09/10] drivers/md/dm-qcow2: allow shared L1 entries during merge from RO image Andrey Zhadchenko
2026-08-12 18:56 ` [Devel] [PATCH VZ10 10/10] drivers/md/dm-qcow2: respect zeroes during merge Andrey Zhadchenko
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260812185638.110779-7-andrey.zhadchenko@virtuozzo.com \
--to=andrey.zhadchenko@virtuozzo.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox