[PATCH 2/6] iommupt: allow full-table contiguous leaves in unit tests

From: Daniel Drake

Date: Sun Jul 12 2026 - 18:41:22 EST


The generic_pt tests currently assume that a format should never support
a contiguous page size that spans the entire mapping range of the table
it resides in.

This assumption breaks for BCM2712, which has a 2-level page table.
Level 1 entries cover 4MB of memory via their corresponding Level 0
table pages. There is a concept of a 4MB largepage, but that is
represented as a Level 1 entry pointing at a Level 0 table with the 1024
usual PTEs with an additional largepage hint applied (for TLB
optimization purposes). Unlike typical IOMMUs, it is not possible to have
the Level 1 entry point directly at a 4MB chunk without the need for
Level 0 mapping.

This BCM2712 format is well supported by generic_pt: SZ_4M can be
advertised as a possible size at Level 0 (so that the iommu core feeds
it 4MB-aligned mapping requests where possible), and drivers can also
advertise that *all* leaf entries are installed at Level 0. This indicates
that the tests are overly strict.

Transform the test to become a validation of the driver's routing intent:
for every advertised page size, check that pt_pgsz_lg2_to_level() maps that
size back to the table level under test. This is roughly what the original
tests were doing, but also allows the BCM2712 case to pass.

Signed-off-by: Daniel Drake <dan@xxxxxxxxxxxxxxx>
---
drivers/iommu/generic_pt/kunit_generic_pt.h | 39 +++++++++++------------------
1 file changed, 14 insertions(+), 25 deletions(-)

diff --git a/drivers/iommu/generic_pt/kunit_generic_pt.h b/drivers/iommu/generic_pt/kunit_generic_pt.h
index ef2c90b6d6af..7f4e172ef08b 100644
--- a/drivers/iommu/generic_pt/kunit_generic_pt.h
+++ b/drivers/iommu/generic_pt/kunit_generic_pt.h
@@ -419,40 +419,35 @@ static void test_table_radix(struct kunit *test)
}
}

-static unsigned int safe_pt_num_items_lg2(const struct pt_state *pts)
-{
- struct pt_range top_range = pt_top_range(pts->range->common);
- struct pt_state top_pts = pt_init_top(&top_range);
-
- /*
- * Avoid calling pt_num_items_lg2() on the top, instead we can derive
- * the size of the top table from the top range.
- */
- if (pts->level == top_range.top_level)
- return ilog2(pt_range_to_end_index(&top_pts));
- return pt_num_items_lg2(pts);
-}
-
static void test_lvl_possible_sizes(struct kunit *test, struct pt_state *pts,
void *arg)
{
- unsigned int num_items_lg2 = safe_pt_num_items_lg2(pts);
pt_vaddr_t pgsize_bitmap = pt_possible_sizes(pts);
/* Matches get_info() */
pt_vaddr_t limited_pgsize_bitmap =
log2_mod(pgsize_bitmap, pts->range->common->max_vasz_lg2 - 1);
unsigned int isz_lg2 = pt_table_item_lg2sz(pts);
+ unsigned int sz_lg2;

if (!pt_can_have_leaf(pts)) {
KUNIT_ASSERT_EQ(test, pgsize_bitmap, 0);
return;
}

- /* No bits for sizes that would be outside this table */
+ /* A page size cannot be smaller than the span of a single entry. */
KUNIT_ASSERT_EQ(test, log2_mod(pgsize_bitmap, isz_lg2), 0);
- KUNIT_ASSERT_EQ(
- test,
- fvalog2_div(limited_pgsize_bitmap, num_items_lg2 + isz_lg2), 0);
+
+ /*
+ * Ensure every page size claimed to be supported at this level
+ * actually routes back to this level
+ */
+ for (sz_lg2 = 0; sz_lg2 < PT_VADDR_MAX_LG2; sz_lg2++) {
+ if (limited_pgsize_bitmap & log2_to_int(sz_lg2)) {
+ KUNIT_ASSERT_EQ(test,
+ pt_pgsz_lg2_to_level(pts->range->common, sz_lg2),
+ pts->level);
+ }
+ }

/*
* Non contiguous must be supported. AMDv1 has a HW bug where it does
@@ -463,12 +458,6 @@ static void test_lvl_possible_sizes(struct kunit *test, struct pt_state *pts,
KUNIT_ASSERT_TRUE(test, pgsize_bitmap & log2_to_int(isz_lg2));
else
KUNIT_ASSERT_NE(test, pgsize_bitmap, 0);
-
- /* A contiguous entry should not span the whole table */
- if (num_items_lg2 + isz_lg2 != PT_VADDR_MAX_LG2)
- KUNIT_ASSERT_FALSE(
- test, limited_pgsize_bitmap &
- log2_to_int(num_items_lg2 + isz_lg2));
}

static void test_entry_possible_sizes(struct kunit *test)

--
2.55.0