Skip to content

Commit d856f9d

Browse files
jgunthorpejoergroedel
authored andcommitted
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: d373449 ("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>
1 parent 416d9a2 commit d856f9d

4 files changed

Lines changed: 35 additions & 20 deletions

File tree

drivers/iommu/generic_pt/fmt/vtdss.h

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -248,18 +248,11 @@ static inline int vtdss_pt_iommu_fmt_init(struct pt_iommu_vtdss *iommu_table,
248248
const struct pt_iommu_vtdss_cfg *cfg)
249249
{
250250
struct pt_vtdss *table = &iommu_table->vtdss_pt;
251-
unsigned int vasz_lg2 = cfg->common.hw_max_vasz_lg2;
252251

253-
if (vasz_lg2 > PT_MAX_VA_ADDRESS_LG2)
254-
return -EOPNOTSUPP;
255-
else if (vasz_lg2 > 48)
256-
pt_top_set_level(&table->common, 4);
257-
else if (vasz_lg2 > 39)
258-
pt_top_set_level(&table->common, 3);
259-
else if (vasz_lg2 > 30)
260-
pt_top_set_level(&table->common, 2);
261-
else
252+
if (cfg->top_level > 4 || cfg->top_level < 2)
262253
return -EOPNOTSUPP;
254+
255+
pt_top_set_level(&table->common, cfg->top_level);
263256
return 0;
264257
}
265258
#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,
282275

283276
#if defined(GENERIC_PT_KUNIT)
284277
static const struct pt_iommu_vtdss_cfg vtdss_kunit_fmt_cfgs[] = {
285-
[0] = { .common.hw_max_vasz_lg2 = 39 },
286-
[1] = { .common.hw_max_vasz_lg2 = 48 },
287-
[2] = { .common.hw_max_vasz_lg2 = 57 },
278+
[0] = { .common.hw_max_vasz_lg2 = 39, .top_level = 2},
279+
[1] = { .common.hw_max_vasz_lg2 = 48, .top_level = 3},
280+
[2] = { .common.hw_max_vasz_lg2 = 57, .top_level = 4},
288281
};
289282
#define kunit_fmt_cfgs vtdss_kunit_fmt_cfgs
290283
enum { KUNIT_FMT_FEATURES = BIT(PT_FEAT_VTDSS_FORCE_WRITEABLE) };

drivers/iommu/generic_pt/iommu_pt.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1128,6 +1128,20 @@ static int pt_init_common(struct pt_common *common)
11281128
PT_FORCE_ENABLED_FEATURES))
11291129
return -EOPNOTSUPP;
11301130

1131+
/*
1132+
* Check if the top level of the page table is too small to hold the
1133+
* specified maxvasz.
1134+
*/
1135+
if (!pt_feature(common, PT_FEAT_DYNAMIC_TOP) &&
1136+
top_range.top_level != PT_MAX_TOP_LEVEL) {
1137+
struct pt_state pts = { .range = &top_range,
1138+
.level = top_range.top_level };
1139+
1140+
if (common->max_vasz_lg2 >
1141+
pt_num_items_lg2(&pts) + pt_table_item_lg2sz(&pts))
1142+
return -EOPNOTSUPP;
1143+
}
1144+
11311145
if (common->max_oasz_lg2 == 0)
11321146
common->max_oasz_lg2 = pt_max_oa_lg2(common);
11331147
else

drivers/iommu/intel/iommu.c

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2858,22 +2858,28 @@ intel_iommu_domain_alloc_first_stage(struct device *dev,
28582858
return &dmar_domain->domain;
28592859
}
28602860

2861-
static int compute_vasz_lg2_ss(struct intel_iommu *iommu)
2861+
static unsigned int compute_vasz_lg2_ss(struct intel_iommu *iommu,
2862+
unsigned int *top_level)
28622863
{
28632864
unsigned int sagaw = cap_sagaw(iommu->cap);
28642865
unsigned int mgaw = cap_mgaw(iommu->cap);
28652866

28662867
/*
28672868
* Find the largest table size that both the mgaw and sagaw support.
2868-
* This sets both the number of table levels and the valid range of
2869-
* IOVA.
2869+
* This sets the valid range of IOVA and the top starting level.
2870+
* Some HW may only support a 4 or 5 level walk but must limit IOVA to
2871+
* 3 levels.
28702872
*/
2871-
if (mgaw >= 48 && (sagaw & BIT(3)))
2873+
if (mgaw > 48 && sagaw >= BIT(3)) {
2874+
*top_level = 4;
28722875
return min(57, mgaw);
2873-
else if (mgaw >= 39 && (sagaw & BIT(2)))
2876+
} else if (mgaw > 39 && sagaw >= BIT(2)) {
2877+
*top_level = 3 + ffs(sagaw >> 3);
28742878
return min(48, mgaw);
2875-
else if (mgaw >= 30 && (sagaw & BIT(1)))
2879+
} else if (mgaw > 30 && sagaw >= BIT(1)) {
2880+
*top_level = 2 + ffs(sagaw >> 2);
28762881
return min(39, mgaw);
2882+
}
28772883
return 0;
28782884
}
28792885

@@ -2910,7 +2916,7 @@ intel_iommu_domain_alloc_second_stage(struct device *dev,
29102916
if (IS_ERR(dmar_domain))
29112917
return ERR_CAST(dmar_domain);
29122918

2913-
cfg.common.hw_max_vasz_lg2 = compute_vasz_lg2_ss(iommu);
2919+
cfg.common.hw_max_vasz_lg2 = compute_vasz_lg2_ss(iommu, &cfg.top_level);
29142920
cfg.common.hw_max_oasz_lg2 = 52;
29152921
cfg.common.features = BIT(PT_FEAT_FLUSH_RANGE);
29162922

include/linux/generic_pt/iommu.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,8 @@ IOMMU_PROTOTYPES(amdv1_mock);
264264

265265
struct pt_iommu_vtdss_cfg {
266266
struct pt_iommu_cfg common;
267+
/* 4 is a 57 bit 5 level table */
268+
unsigned int top_level;
267269
};
268270

269271
struct pt_iommu_vtdss_hw_info {

0 commit comments

Comments
 (0)