* [PATCH 0/2] iommupt: fix zero-length unmap causing a full IOTLB flush
@ 2026-11-05 12:12 YeeLi
2026-11-05 12:12 ` [PATCH 1/2] iommupt: fix full IOTLB flush from a zero length unmap YeeLi
2026-11-05 12:12 ` [PATCH 2/2] iommupt: add a KUnit test for unmapping an empty range YeeLi
0 siblings, 2 replies; 4+ messages in thread
From: YeeLi @ 2026-11-05 12:12 UTC (permalink / raw)
To: joro, will; +Cc: robin.murphy, jgg, iommu
From: yeeli <seven.yi.lee@gmail.com>
This series fixes an unmap that removes nothing from being turned into a
full-domain IOTLB flush, and adds a KUnit test for it.
Disclosure of tool use: the bug analysis and the initial drafts of both
patches were produced with the help of an AI coding assistant
(DeepSeek-V4.1-Flash, running in OpenCode). The prompts were a review of
the Intel IOMMU generic_pt integration, followed by requests to
reproduce, fix and test the empty unmap behaviour found there. The
assistant reviewed the iommupt unmap path and proposed the len guard in
gather_range_pending() plus the KUnit test; the code, commit messages
and test were then reviewed by the submitter. The submitter also
verified the behaviour independently: KUnit on the vtdss format, where
the new test fails without the fix and passes with it (full
vtdss_iommu_test: 63 tests, 60 passed, 3 skipped, 0 failed), and an
out-of-tree module on an Intel VT-d system observed with the
cache_tag_flush_range and qi_submit tracepoints. Everything that was
attempted could be built and tested. The submitter takes responsibility
for the submission.
yeeli (2):
iommupt: fix full IOTLB flush from a zero length unmap
iommupt: add a KUnit test for unmapping an empty range
drivers/iommu/generic_pt/iommu_pt.h | 7 ++++-
drivers/iommu/generic_pt/kunit_iommu_pt.h | 31 +++++++++++++++++++++++
2 files changed, 37 insertions(+), 1 deletion(-)
--
2.34.1
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH 1/2] iommupt: fix full IOTLB flush from a zero length unmap
2026-11-05 12:12 [PATCH 0/2] iommupt: fix zero-length unmap causing a full IOTLB flush YeeLi
@ 2026-11-05 12:12 ` YeeLi
2026-11-09 6:59 ` Baolu Lu
2026-11-05 12:12 ` [PATCH 2/2] iommupt: add a KUnit test for unmapping an empty range YeeLi
1 sibling, 1 reply; 4+ messages in thread
From: YeeLi @ 2026-11-05 12:12 UTC (permalink / raw)
To: joro, will; +Cc: robin.murphy, jgg, iommu
From: yeeli <seven.yi.lee@gmail.com>
unmap_range() hands gather_range_pending() the number of bytes it
actually unmapped, not the requested length. That count is 0 whenever
the range holds no mappings, so an unmap that removes nothing reaches
the gather code with len == 0.
The gather helpers assume a real range: both
iommu_iotlb_gather_add_range() and iommu_iotlb_gather_is_disjoint()
compute end = iova + len - 1. For len == 0 this wraps to iova - 1.
The problem can be reproduced by attaching an empty paging domain to an
Intel VT-d device and unmapping IOVA 0:
domain = iommu_paging_domain_alloc(dev);
iommu_attach_device(domain, dev);
iommu_unmap(domain, 0, PAGE_SIZE); /* nothing is mapped */
The unmap returns 0, but the gather becomes [0, ULONG_MAX] and the
iotlb_sync() that follows asks the driver to invalidate everything.
Captured with the cache_tag_flush_range and qi_submit tracepoints
enabled:
cache_tag_flush_range: dmar0 dmar0[0] type iotlb did 12 [0x0-0xffffffffffffffff] addr 0x0 mask 0x34
qi_submit: p_iotlb_inv dmar0: 0xc0026 0x0 0x0 0x0
With a non-zero IOVA the wrapped end widens whatever range has already
been gathered instead.
Skip the gather bookkeeping when nothing was unmapped. An untouched
gather keeps start == ULONG_MAX and end == 0, which iommu_iotlb_sync()
already ignores.
Fixes: 7c53f4238aa8 ("iommupt: Add unmap_pages op")
Signed-off-by: yeeli <seven.yi.lee@gmail.com>
---
drivers/iommu/generic_pt/iommu_pt.h | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/drivers/iommu/generic_pt/iommu_pt.h b/drivers/iommu/generic_pt/iommu_pt.h
index 837d835e4fad..566cae4bc217 100644
--- a/drivers/iommu/generic_pt/iommu_pt.h
+++ b/drivers/iommu/generic_pt/iommu_pt.h
@@ -83,8 +83,13 @@ static void gather_range_pending(struct iommupt_pending_gather *pending,
* For NO_GAPS the user gets to pick if flushing all or doing micro
* flushes is better for their work load by choosing DMA vs DMA-FQ
* operation. Drivers should also see shadow_on_flush.
+ *
+ * A zero length unmap (len == 0) means nothing was unmapped and must
+ * not create a gather range: iova + len - 1 wraps around, and at IOVA 0
+ * the resulting degenerate range [0, ULONG_MAX] would turn into a full
+ * IOTLB flush.
*/
- if (!iommu_iotlb_gather_queued(iotlb_gather)) {
+ if (len && !iommu_iotlb_gather_queued(iotlb_gather)) {
if (pt_feature(common, PT_FEAT_FLUSH_RANGE_NO_GAPS) &&
iommu_iotlb_gather_is_disjoint(iotlb_gather, iova, len)) {
iommu_iotlb_sync(&iommu_table->domain, iotlb_gather);
--
2.34.1
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH 2/2] iommupt: add a KUnit test for unmapping an empty range
2026-11-05 12:12 [PATCH 0/2] iommupt: fix zero-length unmap causing a full IOTLB flush YeeLi
2026-11-05 12:12 ` [PATCH 1/2] iommupt: fix full IOTLB flush from a zero length unmap YeeLi
@ 2026-11-05 12:12 ` YeeLi
1 sibling, 0 replies; 4+ messages in thread
From: YeeLi @ 2026-11-05 12:12 UTC (permalink / raw)
To: joro, will; +Cc: robin.murphy, jgg, iommu
From: yeeli <seven.yi.lee@gmail.com>
Unmapping a range that holds no mappings must not record anything in
the IOTLB gather. Otherwise iova + len - 1 underflows and the flush
covers the whole domain, or widens a range that was already gathered.
Check that the gather stays empty for a hole at IOVA 0 and at a
non-zero IOVA. Without the previous fix the first case turns the gather
into [0, ULONG_MAX] and the second one flips it to [iova, iova - 1].
Signed-off-by: yeeli <seven.yi.lee@gmail.com>
---
drivers/iommu/generic_pt/kunit_iommu_pt.h | 31 +++++++++++++++++++++++
1 file changed, 31 insertions(+)
diff --git a/drivers/iommu/generic_pt/kunit_iommu_pt.h b/drivers/iommu/generic_pt/kunit_iommu_pt.h
index 9fc71db74234..59a9386f1474 100644
--- a/drivers/iommu/generic_pt/kunit_iommu_pt.h
+++ b/drivers/iommu/generic_pt/kunit_iommu_pt.h
@@ -427,6 +427,36 @@ static void test_mixed(struct kunit *test)
check_iova(test, start, oa, len);
}
+/*
+ * Unmapping a range that holds no mappings must not record anything in
+ * the IOTLB gather. Otherwise iova + len - 1 underflows and the flush
+ * covers the whole domain, or widens a range that was already gathered.
+ */
+static void test_unmap_hole(struct kunit *test)
+{
+ struct kunit_iommu_priv *priv = test->priv;
+ struct iommu_iotlb_gather gather;
+
+ /* A hole at IOVA 0 must not turn the gather into [0, ULONG_MAX]. */
+ iommu_iotlb_gather_init(&gather);
+ KUNIT_ASSERT_EQ(
+ test,
+ iommu_unmap_fast(&priv->domain, 0, priv->smallest_pgsz, &gather),
+ 0);
+ KUNIT_EXPECT_EQ(test, gather.start, ULONG_MAX);
+ KUNIT_EXPECT_EQ(test, gather.end, 0);
+
+ /* Neither may a hole at a non-zero IOVA widen the gather. */
+ iommu_iotlb_gather_init(&gather);
+ KUNIT_ASSERT_EQ(
+ test,
+ iommu_unmap_fast(&priv->domain, priv->smallest_pgsz,
+ priv->smallest_pgsz, &gather),
+ 0);
+ KUNIT_EXPECT_EQ(test, gather.start, ULONG_MAX);
+ KUNIT_EXPECT_EQ(test, gather.end, 0);
+}
+
static struct kunit_case iommu_test_cases[] = {
KUNIT_CASE_FMT(test_increase_level),
KUNIT_CASE_FMT(test_map_simple),
@@ -435,6 +465,7 @@ static struct kunit_case iommu_test_cases[] = {
KUNIT_CASE_FMT(test_random_map),
KUNIT_CASE_FMT(test_pgsize_boundary),
KUNIT_CASE_FMT(test_mixed),
+ KUNIT_CASE_FMT(test_unmap_hole),
{},
};
--
2.34.1
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH 1/2] iommupt: fix full IOTLB flush from a zero length unmap
2026-11-05 12:12 ` [PATCH 1/2] iommupt: fix full IOTLB flush from a zero length unmap YeeLi
@ 2026-11-09 6:59 ` Baolu Lu
0 siblings, 0 replies; 4+ messages in thread
From: Baolu Lu @ 2026-11-09 6:59 UTC (permalink / raw)
To: YeeLi, joro, will; +Cc: baolu.lu, robin.murphy, jgg, iommu
On 11/5/2026 8:12 PM, YeeLi wrote:
> From: yeeli<seven.yi.lee@gmail.com>
>
> unmap_range() hands gather_range_pending() the number of bytes it
> actually unmapped, not the requested length. That count is 0 whenever
> the range holds no mappings, so an unmap that removes nothing reaches
> the gather code with len == 0.
>
> The gather helpers assume a real range: both
> iommu_iotlb_gather_add_range() and iommu_iotlb_gather_is_disjoint()
> compute end = iova + len - 1. For len == 0 this wraps to iova - 1.
>
> The problem can be reproduced by attaching an empty paging domain to an
> Intel VT-d device and unmapping IOVA 0:
>
> domain = iommu_paging_domain_alloc(dev);
> iommu_attach_device(domain, dev);
> iommu_unmap(domain, 0, PAGE_SIZE); /* nothing is mapped */
>
> The unmap returns 0, but the gather becomes [0, ULONG_MAX] and the
> iotlb_sync() that follows asks the driver to invalidate everything.
> Captured with the cache_tag_flush_range and qi_submit tracepoints
> enabled:
>
> cache_tag_flush_range: dmar0 dmar0[0] type iotlb did 12 [0x0-0xffffffffffffffff] addr 0x0 mask 0x34
> qi_submit: p_iotlb_inv dmar0: 0xc0026 0x0 0x0 0x0
>
> With a non-zero IOVA the wrapped end widens whatever range has already
> been gathered instead.
>
> Skip the gather bookkeeping when nothing was unmapped. An untouched
> gather keeps start == ULONG_MAX and end == 0, which iommu_iotlb_sync()
> already ignores.
>
> Fixes: 7c53f4238aa8 ("iommupt: Add unmap_pages op")
> Signed-off-by: yeeli<seven.yi.lee@gmail.com>
> ---
> drivers/iommu/generic_pt/iommu_pt.h | 7 ++++++-
> 1 file changed, 6 insertions(+), 1 deletion(-)
Reviewed-by: Lu Baolu <baolu.lu@linux.intel.com>
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-11-09 6:59 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-05 12:12 [PATCH 0/2] iommupt: fix zero-length unmap causing a full IOTLB flush YeeLi
2026-11-05 12:12 ` [PATCH 1/2] iommupt: fix full IOTLB flush from a zero length unmap YeeLi
2026-11-09 6:59 ` Baolu Lu
2026-11-05 12:12 ` [PATCH 2/2] iommupt: add a KUnit test for unmapping an empty range YeeLi
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.