mirror of
https://github.com/Motorhead1991/qemu.git
synced 2025-08-02 07:13:54 -06:00
intel_iommu: Implement stage-1 translation
This adds stage-1 page table walking to support stage-1 only translation in scalable mode. Signed-off-by: Yi Liu <yi.l.liu@intel.com> Co-developed-by: Clément Mathieu--Drif <clement.mathieu--drif@eviden.com> Signed-off-by: Clément Mathieu--Drif <clement.mathieu--drif@eviden.com> Signed-off-by: Yi Sun <yi.y.sun@linux.intel.com> Signed-off-by: Zhenzhong Duan <zhenzhong.duan@intel.com> Acked-by: Jason Wang <jasowang@redhat.com> Message-Id: <20241212083757.605022-7-zhenzhong.duan@intel.com> Reviewed-by: Michael S. Tsirkin <mst@redhat.com> Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
This commit is contained in:
parent
eda4c9b5b3
commit
eb9da9d263
2 changed files with 188 additions and 4 deletions
|
@ -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;
|
||||
|
|
|
@ -320,6 +320,15 @@ typedef enum VTDFaultReason {
|
|||
VTD_FR_PASID_ENTRY_P = 0x59,
|
||||
VTD_FR_PASID_TABLE_ENTRY_INV = 0x5b, /*Invalid PASID table entry */
|
||||
|
||||
/* Fail to access a first-level paging entry (not FS_PML4E) */
|
||||
VTD_FR_FS_PAGING_ENTRY_INV = 0x70,
|
||||
VTD_FR_FS_PAGING_ENTRY_P = 0x71,
|
||||
/* Non-zero reserved field in present first-stage paging entry */
|
||||
VTD_FR_FS_PAGING_ENTRY_RSVD = 0x72,
|
||||
VTD_FR_PASID_ENTRY_FSPTPTR_INV = 0x73, /* Invalid FSPTPTR in PASID entry */
|
||||
VTD_FR_FS_PAGING_ENTRY_US = 0x81, /* Privilege violation */
|
||||
VTD_FR_SM_WRITE = 0x85, /* No write permission */
|
||||
|
||||
/* Output address in the interrupt address range for scalable mode */
|
||||
VTD_FR_SM_INTERRUPT_ADDR = 0x87,
|
||||
VTD_FR_MAX, /* Guard */
|
||||
|
@ -438,6 +447,22 @@ typedef union VTDInvDesc VTDInvDesc;
|
|||
(0x3ffff800ULL | ~(VTD_HAW_MASK(aw) | VTD_SL_IGN_COM | VTD_SL_TM)) : \
|
||||
(0x3ffff800ULL | ~(VTD_HAW_MASK(aw) | VTD_SL_IGN_COM))
|
||||
|
||||
/* Rsvd field masks for fpte */
|
||||
#define VTD_FS_UPPER_IGNORED 0xfff0000000000000ULL
|
||||
#define VTD_FPTE_PAGE_L1_RSVD_MASK(aw) \
|
||||
(~(VTD_HAW_MASK(aw) | VTD_FS_UPPER_IGNORED))
|
||||
#define VTD_FPTE_PAGE_L2_RSVD_MASK(aw) \
|
||||
(~(VTD_HAW_MASK(aw) | VTD_FS_UPPER_IGNORED))
|
||||
#define VTD_FPTE_PAGE_L3_RSVD_MASK(aw) \
|
||||
(~(VTD_HAW_MASK(aw) | VTD_FS_UPPER_IGNORED))
|
||||
#define VTD_FPTE_PAGE_L4_RSVD_MASK(aw) \
|
||||
(0x80ULL | ~(VTD_HAW_MASK(aw) | VTD_FS_UPPER_IGNORED))
|
||||
|
||||
#define VTD_FPTE_LPAGE_L2_RSVD_MASK(aw) \
|
||||
(0x1fe000ULL | ~(VTD_HAW_MASK(aw) | VTD_FS_UPPER_IGNORED))
|
||||
#define VTD_FPTE_LPAGE_L3_RSVD_MASK(aw) \
|
||||
(0x3fffe000ULL | ~(VTD_HAW_MASK(aw) | VTD_FS_UPPER_IGNORED))
|
||||
|
||||
/* Masks for PIOTLB Invalidate Descriptor */
|
||||
#define VTD_INV_DESC_PIOTLB_G (3ULL << 4)
|
||||
#define VTD_INV_DESC_PIOTLB_ALL_IN_PASID (2ULL << 4)
|
||||
|
@ -530,6 +555,15 @@ typedef struct VTDRootEntry VTDRootEntry;
|
|||
#define VTD_SM_PASID_ENTRY_AW 7ULL /* Adjusted guest-address-width */
|
||||
#define VTD_SM_PASID_ENTRY_DID(val) ((val) & VTD_DOMAIN_ID_MASK)
|
||||
|
||||
#define VTD_SM_PASID_ENTRY_FLPM 3ULL
|
||||
#define VTD_SM_PASID_ENTRY_FLPTPTR (~0xfffULL)
|
||||
|
||||
/* First Level Paging Structure */
|
||||
/* Masks for First Level Paging Entry */
|
||||
#define VTD_FL_P 1ULL
|
||||
#define VTD_FL_RW (1ULL << 1)
|
||||
#define VTD_FL_US (1ULL << 2)
|
||||
|
||||
/* Second Level Page Translation Pointer*/
|
||||
#define VTD_SM_PASID_ENTRY_SLPTPTR (~0xfffULL)
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue