]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
iommupt/vtd: Allow VT-d to have a larger table top than the vasz requires
authorJason Gunthorpe <jgg@nvidia.com>
Thu, 27 Nov 2025 23:54:07 +0000 (19:54 -0400)
committerJoerg Roedel <joerg.roedel@amd.com>
Fri, 28 Nov 2025 07:43:03 +0000 (08:43 +0100)
VT-d second stage HW specifies both the maximum IOVA and the supported
table walk starting points. Weirdly there is HW that only supports a 4
level walk but has a maximum IOVA that only needs 3.

The current code miscalculates this and creates a wrongly sized page table
which ultimately fails the compatibility check for number of levels.

This is fixed by allowing the page table to be created with both a vasz
and top_level input. The vasz will set the aperture for the domain while
the top_level will set the page table geometry.

Add top_level to vtdss and correct the logic in VT-d to generate the right
top_level and vasz from mgaw and sagaw.

Fixes: d373449d8e97 ("iommu/vt-d: Use the generic iommu page table")
Reported-by: Calvin Owens <calvin@wbinvd.org>
Closes: https://lore.kernel.org/r/8f257d2651eb8a4358fcbd47b0145002e5f1d638.1764237717.git.calvin@wbinvd.org
Signed-off-by: Jason Gunthorpe <jgg@nvidia.com>
Reviewed-by: Lu Baolu <baolu.lu@linux.intel.com>
Tested-by: Calvin Owens <calvin@wbinvd.org>
Signed-off-by: Joerg Roedel <joerg.roedel@amd.com>
drivers/iommu/generic_pt/fmt/vtdss.h
drivers/iommu/generic_pt/iommu_pt.h
drivers/iommu/intel/iommu.c
include/linux/generic_pt/iommu.h

index 50ffed9d0e508f2c2ec403d0c2349c3422f2e153..f5f8981edde72e6c60989291351bbb1b4855d1a7 100644 (file)
@@ -248,18 +248,11 @@ static inline int vtdss_pt_iommu_fmt_init(struct pt_iommu_vtdss *iommu_table,
                                          const struct pt_iommu_vtdss_cfg *cfg)
 {
        struct pt_vtdss *table = &iommu_table->vtdss_pt;
-       unsigned int vasz_lg2 = cfg->common.hw_max_vasz_lg2;
 
-       if (vasz_lg2 > PT_MAX_VA_ADDRESS_LG2)
-               return -EOPNOTSUPP;
-       else if (vasz_lg2 > 48)
-               pt_top_set_level(&table->common, 4);
-       else if (vasz_lg2 > 39)
-               pt_top_set_level(&table->common, 3);
-       else if (vasz_lg2 > 30)
-               pt_top_set_level(&table->common, 2);
-       else
+       if (cfg->top_level > 4 || cfg->top_level < 2)
                return -EOPNOTSUPP;
+
+       pt_top_set_level(&table->common, cfg->top_level);
        return 0;
 }
 #define pt_iommu_fmt_init vtdss_pt_iommu_fmt_init
@@ -282,9 +275,9 @@ vtdss_pt_iommu_fmt_hw_info(struct pt_iommu_vtdss *table,
 
 #if defined(GENERIC_PT_KUNIT)
 static const struct pt_iommu_vtdss_cfg vtdss_kunit_fmt_cfgs[] = {
-       [0] = { .common.hw_max_vasz_lg2 = 39 },
-       [1] = { .common.hw_max_vasz_lg2 = 48 },
-       [2] = { .common.hw_max_vasz_lg2 = 57 },
+       [0] = { .common.hw_max_vasz_lg2 = 39, .top_level = 2},
+       [1] = { .common.hw_max_vasz_lg2 = 48, .top_level = 3},
+       [2] = { .common.hw_max_vasz_lg2 = 57, .top_level = 4},
 };
 #define kunit_fmt_cfgs vtdss_kunit_fmt_cfgs
 enum { KUNIT_FMT_FEATURES = BIT(PT_FEAT_VTDSS_FORCE_WRITEABLE) };
index 032d04ec7b568d91bfa1d62c76e62bf9dc5d3725..97aeda1ad01cca10120c2dbdb9e69cc04c2421f2 100644 (file)
@@ -1128,6 +1128,20 @@ static int pt_init_common(struct pt_common *common)
                     PT_FORCE_ENABLED_FEATURES))
                return -EOPNOTSUPP;
 
+       /*
+        * Check if the top level of the page table is too small to hold the
+        * specified maxvasz.
+        */
+       if (!pt_feature(common, PT_FEAT_DYNAMIC_TOP) &&
+           top_range.top_level != PT_MAX_TOP_LEVEL) {
+               struct pt_state pts = { .range = &top_range,
+                                       .level = top_range.top_level };
+
+               if (common->max_vasz_lg2 >
+                   pt_num_items_lg2(&pts) + pt_table_item_lg2sz(&pts))
+                       return -EOPNOTSUPP;
+       }
+
        if (common->max_oasz_lg2 == 0)
                common->max_oasz_lg2 = pt_max_oa_lg2(common);
        else
index 7b3016491ca58609ca6e1063fde75e74db9630e5..f117349d67dbf8b6255769a13e16d046e4ef1cf5 100644 (file)
@@ -2858,22 +2858,28 @@ intel_iommu_domain_alloc_first_stage(struct device *dev,
        return &dmar_domain->domain;
 }
 
-static int compute_vasz_lg2_ss(struct intel_iommu *iommu)
+static unsigned int compute_vasz_lg2_ss(struct intel_iommu *iommu,
+                                       unsigned int *top_level)
 {
        unsigned int sagaw = cap_sagaw(iommu->cap);
        unsigned int mgaw = cap_mgaw(iommu->cap);
 
        /*
         * Find the largest table size that both the mgaw and sagaw support.
-        * This sets both the number of table levels and the valid range of
-        * IOVA.
+        * This sets the valid range of IOVA and the top starting level.
+        * Some HW may only support a 4 or 5 level walk but must limit IOVA to
+        * 3 levels.
         */
-       if (mgaw >= 48 && (sagaw & BIT(3)))
+       if (mgaw > 48 && sagaw >= BIT(3)) {
+               *top_level = 4;
                return min(57, mgaw);
-       else if (mgaw >= 39 && (sagaw & BIT(2)))
+       } else if (mgaw > 39 && sagaw >= BIT(2)) {
+               *top_level = 3 + ffs(sagaw >> 3);
                return min(48, mgaw);
-       else if (mgaw >= 30 && (sagaw & BIT(1)))
+       } else if (mgaw > 30 && sagaw >= BIT(1)) {
+               *top_level = 2 + ffs(sagaw >> 2);
                return min(39, mgaw);
+       }
        return 0;
 }
 
@@ -2910,7 +2916,7 @@ intel_iommu_domain_alloc_second_stage(struct device *dev,
        if (IS_ERR(dmar_domain))
                return ERR_CAST(dmar_domain);
 
-       cfg.common.hw_max_vasz_lg2 = compute_vasz_lg2_ss(iommu);
+       cfg.common.hw_max_vasz_lg2 = compute_vasz_lg2_ss(iommu, &cfg.top_level);
        cfg.common.hw_max_oasz_lg2 = 52;
        cfg.common.features = BIT(PT_FEAT_FLUSH_RANGE);
 
index cfe05a77f86b058103e1398c84d58031ce20fd7c..c134132ed10f8938d2549eda45d55f62c6e4fb63 100644 (file)
@@ -264,6 +264,8 @@ IOMMU_PROTOTYPES(amdv1_mock);
 
 struct pt_iommu_vtdss_cfg {
        struct pt_iommu_cfg common;
+       /* 4 is a 57 bit 5 level table */
+       unsigned int top_level;
 };
 
 struct pt_iommu_vtdss_hw_info {