diff options
| author | Jason Gunthorpe <jgg@nvidia.com> | 2025-11-27 19:54:07 -0400 |
|---|---|---|
| committer | Joerg Roedel <joerg.roedel@amd.com> | 2025-11-28 08:43:03 +0100 |
| commit | d856f9d27885c499d96ab7fe506083346ccf145d (patch) | |
| tree | 7a42d454bfd1f0e2f6aec9ffc87571fa7b9fae3d | |
| parent | 416d9a220e678d6b1c5fc206226cfb7fa7efa80e (diff) | |
iommupt/vtd: Allow VT-d to have a larger table top than the vasz requires
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>
| -rw-r--r-- | drivers/iommu/generic_pt/fmt/vtdss.h | 19 | ||||
| -rw-r--r-- | drivers/iommu/generic_pt/iommu_pt.h | 14 | ||||
| -rw-r--r-- | drivers/iommu/intel/iommu.c | 20 | ||||
| -rw-r--r-- | include/linux/generic_pt/iommu.h | 2 |
4 files changed, 35 insertions, 20 deletions
diff --git a/drivers/iommu/generic_pt/fmt/vtdss.h b/drivers/iommu/generic_pt/fmt/vtdss.h index 50ffed9d0e50..f5f8981edde7 100644 --- a/drivers/iommu/generic_pt/fmt/vtdss.h +++ b/drivers/iommu/generic_pt/fmt/vtdss.h @@ -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) }; diff --git a/drivers/iommu/generic_pt/iommu_pt.h b/drivers/iommu/generic_pt/iommu_pt.h index 032d04ec7b56..97aeda1ad01c 100644 --- a/drivers/iommu/generic_pt/iommu_pt.h +++ b/drivers/iommu/generic_pt/iommu_pt.h @@ -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 diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c index 7b3016491ca5..f117349d67db 100644 --- a/drivers/iommu/intel/iommu.c +++ b/drivers/iommu/intel/iommu.c @@ -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); diff --git a/include/linux/generic_pt/iommu.h b/include/linux/generic_pt/iommu.h index cfe05a77f86b..c134132ed10f 100644 --- a/include/linux/generic_pt/iommu.h +++ b/include/linux/generic_pt/iommu.h @@ -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 { |
