|
@@ -48,6 +48,8 @@
|
|
|
|
|
|
/* pe operations */
|
|
|
#define VTD_PE_GET_TYPE(pe) ((pe)->val[0] & VTD_SM_PASID_ENTRY_PGTT)
|
|
|
+#define VTD_PE_GET_FL_LEVEL(pe) \
|
|
|
+ (4 + (((pe)->val[2] >> 2) & VTD_SM_PASID_ENTRY_FLPM))
|
|
|
#define VTD_PE_GET_SL_LEVEL(pe) \
|
|
|
(2 + (((pe)->val[0] >> 2) & VTD_SM_PASID_ENTRY_AW))
|
|
|
|
|
@@ -755,6 +757,11 @@ static inline bool vtd_is_sl_level_supported(IntelIOMMUState *s, uint32_t level)
|
|
|
(1ULL << (level - 2 + VTD_CAP_SAGAW_SHIFT));
|
|
|
}
|
|
|
|
|
|
+static inline bool vtd_is_fl_level_supported(IntelIOMMUState *s, uint32_t level)
|
|
|
+{
|
|
|
+ return level == VTD_PML4_LEVEL;
|
|
|
+}
|
|
|
+
|
|
|
/* Return true if check passed, otherwise false */
|
|
|
static inline bool vtd_pe_type_check(X86IOMMUState *x86_iommu,
|
|
|
VTDPASIDEntry *pe)
|
|
@@ -838,6 +845,11 @@ static int vtd_get_pe_in_pasid_leaf_table(IntelIOMMUState *s,
|
|
|
return -VTD_FR_PASID_TABLE_ENTRY_INV;
|
|
|
}
|
|
|
|
|
|
+ if (pgtt == VTD_SM_PASID_ENTRY_FLT &&
|
|
|
+ !vtd_is_fl_level_supported(s, VTD_PE_GET_FL_LEVEL(pe))) {
|
|
|
+ return -VTD_FR_PASID_TABLE_ENTRY_INV;
|
|
|
+ }
|
|
|
+
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
@@ -973,7 +985,11 @@ static uint32_t vtd_get_iova_level(IntelIOMMUState *s,
|
|
|
|
|
|
if (s->root_scalable) {
|
|
|
vtd_ce_get_rid2pasid_entry(s, ce, &pe, pasid);
|
|
|
- return VTD_PE_GET_SL_LEVEL(&pe);
|
|
|
+ if (s->flts) {
|
|
|
+ return VTD_PE_GET_FL_LEVEL(&pe);
|
|
|
+ } else {
|
|
|
+ return VTD_PE_GET_SL_LEVEL(&pe);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
return vtd_ce_get_level(ce);
|
|
@@ -1060,7 +1076,11 @@ static dma_addr_t vtd_get_iova_pgtbl_base(IntelIOMMUState *s,
|
|
|
|
|
|
if (s->root_scalable) {
|
|
|
vtd_ce_get_rid2pasid_entry(s, ce, &pe, pasid);
|
|
|
- return pe.val[0] & VTD_SM_PASID_ENTRY_SLPTPTR;
|
|
|
+ if (s->flts) {
|
|
|
+ return pe.val[2] & VTD_SM_PASID_ENTRY_FLPTPTR;
|
|
|
+ } else {
|
|
|
+ return pe.val[0] & VTD_SM_PASID_ENTRY_SLPTPTR;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
return vtd_ce_get_slpt_base(ce);
|
|
@@ -1800,6 +1820,12 @@ static const bool vtd_qualified_faults[] = {
|
|
|
[VTD_FR_PASID_TABLE_ACCESS_ERR] = false,
|
|
|
[VTD_FR_PASID_ENTRY_P] = true,
|
|
|
[VTD_FR_PASID_TABLE_ENTRY_INV] = true,
|
|
|
+ [VTD_FR_FS_PAGING_ENTRY_INV] = true,
|
|
|
+ [VTD_FR_FS_PAGING_ENTRY_P] = true,
|
|
|
+ [VTD_FR_FS_PAGING_ENTRY_RSVD] = true,
|
|
|
+ [VTD_FR_PASID_ENTRY_FSPTPTR_INV] = true,
|
|
|
+ [VTD_FR_FS_PAGING_ENTRY_US] = true,
|
|
|
+ [VTD_FR_SM_WRITE] = true,
|
|
|
[VTD_FR_SM_INTERRUPT_ADDR] = true,
|
|
|
[VTD_FR_MAX] = false,
|
|
|
};
|
|
@@ -1862,6 +1888,113 @@ out:
|
|
|
trace_vtd_pt_enable_fast_path(source_id, success);
|
|
|
}
|
|
|
|
|
|
+/*
|
|
|
+ * Rsvd field masks for fpte:
|
|
|
+ * vtd_fpte_rsvd 4k pages
|
|
|
+ * vtd_fpte_rsvd_large large pages
|
|
|
+ *
|
|
|
+ * We support only 4-level page tables.
|
|
|
+ */
|
|
|
+#define VTD_FPTE_RSVD_LEN 5
|
|
|
+static uint64_t vtd_fpte_rsvd[VTD_FPTE_RSVD_LEN];
|
|
|
+static uint64_t vtd_fpte_rsvd_large[VTD_FPTE_RSVD_LEN];
|
|
|
+
|
|
|
+static bool vtd_flpte_nonzero_rsvd(uint64_t flpte, uint32_t level)
|
|
|
+{
|
|
|
+ uint64_t rsvd_mask;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * We should have caught a guest-mis-programmed level earlier,
|
|
|
+ * via vtd_is_fl_level_supported.
|
|
|
+ */
|
|
|
+ assert(level < VTD_FPTE_RSVD_LEN);
|
|
|
+ /*
|
|
|
+ * Zero level doesn't exist. The smallest level is VTD_PT_LEVEL=1 and
|
|
|
+ * checked by vtd_is_last_pte().
|
|
|
+ */
|
|
|
+ assert(level);
|
|
|
+
|
|
|
+ if ((level == VTD_PD_LEVEL || level == VTD_PDP_LEVEL) &&
|
|
|
+ (flpte & VTD_PT_PAGE_SIZE_MASK)) {
|
|
|
+ /* large page */
|
|
|
+ rsvd_mask = vtd_fpte_rsvd_large[level];
|
|
|
+ } else {
|
|
|
+ rsvd_mask = vtd_fpte_rsvd[level];
|
|
|
+ }
|
|
|
+
|
|
|
+ return flpte & rsvd_mask;
|
|
|
+}
|
|
|
+
|
|
|
+static inline bool vtd_flpte_present(uint64_t flpte)
|
|
|
+{
|
|
|
+ return !!(flpte & VTD_FL_P);
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Given the @iova, get relevant @flptep. @flpte_level will be the last level
|
|
|
+ * of the translation, can be used for deciding the size of large page.
|
|
|
+ */
|
|
|
+static int vtd_iova_to_flpte(IntelIOMMUState *s, VTDContextEntry *ce,
|
|
|
+ uint64_t iova, bool is_write,
|
|
|
+ uint64_t *flptep, uint32_t *flpte_level,
|
|
|
+ bool *reads, bool *writes, uint8_t aw_bits,
|
|
|
+ uint32_t pasid)
|
|
|
+{
|
|
|
+ dma_addr_t addr = vtd_get_iova_pgtbl_base(s, ce, pasid);
|
|
|
+ uint32_t level = vtd_get_iova_level(s, ce, pasid);
|
|
|
+ uint32_t offset;
|
|
|
+ uint64_t flpte;
|
|
|
+
|
|
|
+ while (true) {
|
|
|
+ offset = vtd_iova_level_offset(iova, level);
|
|
|
+ flpte = vtd_get_pte(addr, offset);
|
|
|
+
|
|
|
+ if (flpte == (uint64_t)-1) {
|
|
|
+ if (level == vtd_get_iova_level(s, ce, pasid)) {
|
|
|
+ /* Invalid programming of pasid-entry */
|
|
|
+ return -VTD_FR_PASID_ENTRY_FSPTPTR_INV;
|
|
|
+ } else {
|
|
|
+ return -VTD_FR_FS_PAGING_ENTRY_INV;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!vtd_flpte_present(flpte)) {
|
|
|
+ *reads = false;
|
|
|
+ *writes = false;
|
|
|
+ return -VTD_FR_FS_PAGING_ENTRY_P;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* No emulated device supports supervisor privilege request yet */
|
|
|
+ if (!(flpte & VTD_FL_US)) {
|
|
|
+ *reads = false;
|
|
|
+ *writes = false;
|
|
|
+ return -VTD_FR_FS_PAGING_ENTRY_US;
|
|
|
+ }
|
|
|
+
|
|
|
+ *reads = true;
|
|
|
+ *writes = (*writes) && (flpte & VTD_FL_RW);
|
|
|
+ if (is_write && !(flpte & VTD_FL_RW)) {
|
|
|
+ return -VTD_FR_SM_WRITE;
|
|
|
+ }
|
|
|
+ if (vtd_flpte_nonzero_rsvd(flpte, level)) {
|
|
|
+ error_report_once("%s: detected flpte reserved non-zero "
|
|
|
+ "iova=0x%" PRIx64 ", level=0x%" PRIx32
|
|
|
+ "flpte=0x%" PRIx64 ", pasid=0x%" PRIX32 ")",
|
|
|
+ __func__, iova, level, flpte, pasid);
|
|
|
+ return -VTD_FR_FS_PAGING_ENTRY_RSVD;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (vtd_is_last_pte(flpte, level)) {
|
|
|
+ *flptep = flpte;
|
|
|
+ *flpte_level = level;
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ addr = vtd_get_pte_addr(flpte, aw_bits);
|
|
|
+ level--;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
static void vtd_report_fault(IntelIOMMUState *s,
|
|
|
int err, bool is_fpd_set,
|
|
|
uint16_t source_id,
|
|
@@ -2010,8 +2143,13 @@ static bool vtd_do_iommu_translate(VTDAddressSpace *vtd_as, PCIBus *bus,
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- ret_fr = vtd_iova_to_slpte(s, &ce, addr, is_write, &pte, &level,
|
|
|
- &reads, &writes, s->aw_bits, pasid);
|
|
|
+ if (s->flts && s->root_scalable) {
|
|
|
+ ret_fr = vtd_iova_to_flpte(s, &ce, addr, is_write, &pte, &level,
|
|
|
+ &reads, &writes, s->aw_bits, pasid);
|
|
|
+ } else {
|
|
|
+ ret_fr = vtd_iova_to_slpte(s, &ce, addr, is_write, &pte, &level,
|
|
|
+ &reads, &writes, s->aw_bits, pasid);
|
|
|
+ }
|
|
|
if (ret_fr) {
|
|
|
vtd_report_fault(s, -ret_fr, is_fpd_set, source_id,
|
|
|
addr, is_write, pasid != PCI_NO_PASID, pasid);
|
|
@@ -4286,6 +4424,18 @@ static void vtd_init(IntelIOMMUState *s)
|
|
|
vtd_spte_rsvd_large[3] = VTD_SPTE_LPAGE_L3_RSVD_MASK(s->aw_bits,
|
|
|
x86_iommu->dt_supported && s->stale_tm);
|
|
|
|
|
|
+ /*
|
|
|
+ * Rsvd field masks for fpte
|
|
|
+ */
|
|
|
+ vtd_fpte_rsvd[0] = ~0ULL;
|
|
|
+ vtd_fpte_rsvd[1] = VTD_FPTE_PAGE_L1_RSVD_MASK(s->aw_bits);
|
|
|
+ vtd_fpte_rsvd[2] = VTD_FPTE_PAGE_L2_RSVD_MASK(s->aw_bits);
|
|
|
+ vtd_fpte_rsvd[3] = VTD_FPTE_PAGE_L3_RSVD_MASK(s->aw_bits);
|
|
|
+ vtd_fpte_rsvd[4] = VTD_FPTE_PAGE_L4_RSVD_MASK(s->aw_bits);
|
|
|
+
|
|
|
+ vtd_fpte_rsvd_large[2] = VTD_FPTE_LPAGE_L2_RSVD_MASK(s->aw_bits);
|
|
|
+ vtd_fpte_rsvd_large[3] = VTD_FPTE_LPAGE_L3_RSVD_MASK(s->aw_bits);
|
|
|
+
|
|
|
if (s->scalable_mode || s->snoop_control) {
|
|
|
vtd_spte_rsvd[1] &= ~VTD_SPTE_SNP;
|
|
|
vtd_spte_rsvd_large[2] &= ~VTD_SPTE_SNP;
|