diff --git a/hw/i386/intel_iommu.c b/hw/i386/intel_iommu.c index 6a168d5107..c47f13b659 100644 --- a/hw/i386/intel_iommu.c +++ b/hw/i386/intel_iommu.c @@ -3051,6 +3051,130 @@ static bool vtd_process_piotlb_desc(IntelIOMMUState *s, return true; } +static inline int vtd_dev_get_pe_from_pasid(VTDAddressSpace *vtd_as, + VTDPASIDEntry *pe) +{ + IntelIOMMUState *s = vtd_as->iommu_state; + VTDContextEntry ce; + int ret; + + if (!s->root_scalable) { + return -VTD_FR_RTADDR_INV_TTM; + } + + ret = vtd_dev_to_context_entry(s, pci_bus_num(vtd_as->bus), vtd_as->devfn, + &ce); + if (ret) { + return ret; + } + + return vtd_ce_get_rid2pasid_entry(s, &ce, pe, vtd_as->pasid); +} + +/* Update or invalidate pasid cache based on the pasid entry in guest memory. */ +static void vtd_pasid_cache_sync_locked(gpointer key, gpointer value, + gpointer user_data) +{ + VTDPASIDCacheInfo *pc_info = user_data; + VTDAddressSpace *vtd_as = value; + VTDPASIDCacheEntry *pc_entry = &vtd_as->pasid_cache_entry; + VTDPASIDEntry pe; + uint16_t did; + + if (vtd_dev_get_pe_from_pasid(vtd_as, &pe)) { + /* + * No valid pasid entry in guest memory. e.g. pasid entry was modified + * to be either all-zero or non-present. Either case means existing + * pasid cache should be invalidated. + */ + pc_entry->valid = false; + return; + } + + /* + * VTD_INV_DESC_PASIDC_G_DSI and VTD_INV_DESC_PASIDC_G_PASID_SI require + * DID check. If DID doesn't match the value in cache or memory, then + * it's not a pasid entry we want to invalidate. + */ + switch (pc_info->type) { + case VTD_INV_DESC_PASIDC_G_PASID_SI: + if (pc_info->pasid != vtd_as->pasid) { + return; + } + /* Fall through */ + case VTD_INV_DESC_PASIDC_G_DSI: + if (pc_entry->valid) { + did = VTD_SM_PASID_ENTRY_DID(pc_entry->pasid_entry.val[1]); + } else { + did = VTD_SM_PASID_ENTRY_DID(pe.val[1]); + } + if (pc_info->did != did) { + return; + } + } + + pc_entry->pasid_entry = pe; + pc_entry->valid = true; +} + +static void vtd_pasid_cache_sync(IntelIOMMUState *s, VTDPASIDCacheInfo *pc_info) +{ + if (!s->root_scalable || !s->dmar_enabled) { + return; + } + + vtd_iommu_lock(s); + g_hash_table_foreach(s->vtd_address_spaces, vtd_pasid_cache_sync_locked, + pc_info); + vtd_iommu_unlock(s); +} + +static bool vtd_process_pasid_desc(IntelIOMMUState *s, + VTDInvDesc *inv_desc) +{ + uint16_t did; + uint32_t pasid; + VTDPASIDCacheInfo pc_info = {}; + uint64_t mask[4] = {VTD_INV_DESC_PASIDC_RSVD_VAL0, VTD_INV_DESC_ALL_ONE, + VTD_INV_DESC_ALL_ONE, VTD_INV_DESC_ALL_ONE}; + + if (!vtd_inv_desc_reserved_check(s, inv_desc, mask, true, + __func__, "pasid cache inv")) { + return false; + } + + did = VTD_INV_DESC_PASIDC_DID(inv_desc); + pasid = VTD_INV_DESC_PASIDC_PASID(inv_desc); + pc_info.type = VTD_INV_DESC_PASIDC_G(inv_desc); + + switch (pc_info.type) { + case VTD_INV_DESC_PASIDC_G_DSI: + trace_vtd_inv_desc_pasid_cache_dsi(did); + pc_info.did = did; + break; + + case VTD_INV_DESC_PASIDC_G_PASID_SI: + /* PASID selective implies a DID selective */ + trace_vtd_inv_desc_pasid_cache_psi(did, pasid); + pc_info.did = did; + pc_info.pasid = pasid ?: PCI_NO_PASID; + break; + + case VTD_INV_DESC_PASIDC_G_GLOBAL: + trace_vtd_inv_desc_pasid_cache_gsi(); + break; + + default: + error_report_once("invalid granularity field in PASID-cache invalidate " + "descriptor, hi: 0x%"PRIx64" lo: 0x%" PRIx64, + inv_desc->val[1], inv_desc->val[0]); + return false; + } + + vtd_pasid_cache_sync(s, &pc_info); + return true; +} + static bool vtd_process_inv_iec_desc(IntelIOMMUState *s, VTDInvDesc *inv_desc) { @@ -3266,6 +3390,13 @@ static bool vtd_process_inv_desc(IntelIOMMUState *s) } break; + case VTD_INV_DESC_PC: + trace_vtd_inv_desc("pasid-cache", inv_desc.val[1], inv_desc.val[0]); + if (!vtd_process_pasid_desc(s, &inv_desc)) { + return false; + } + break; + case VTD_INV_DESC_PIOTLB: trace_vtd_inv_desc("p-iotlb", inv_desc.val[1], inv_desc.val[0]); if (!vtd_process_piotlb_desc(s, &inv_desc)) { @@ -3308,16 +3439,6 @@ static bool vtd_process_inv_desc(IntelIOMMUState *s) } break; - /* - * TODO: the entity of below two cases will be implemented in future series. - * To make guest (which integrates scalable mode support patch set in - * iommu driver) work, just return true is enough so far. - */ - case VTD_INV_DESC_PC: - if (s->scalable_mode) { - break; - } - /* fallthrough */ default: error_report_once("%s: invalid inv desc: hi=%"PRIx64", lo=%"PRIx64 " (unknown type)", __func__, inv_desc.hi, diff --git a/hw/i386/intel_iommu_internal.h b/hw/i386/intel_iommu_internal.h index 0f6a1237e4..75bafdf0cd 100644 --- a/hw/i386/intel_iommu_internal.h +++ b/hw/i386/intel_iommu_internal.h @@ -316,6 +316,8 @@ typedef enum VTDFaultReason { * request while disabled */ VTD_FR_IR_SID_ERR = 0x26, /* Invalid Source-ID */ + VTD_FR_RTADDR_INV_TTM = 0x31, /* Invalid TTM in RTADDR */ + VTD_FR_SM_PRE_ABS = 0x47, /* SCT.8 : PRE bit in a present SM CE is 0 */ /* PASID directory entry access failure */ @@ -517,6 +519,15 @@ typedef union VTDPRDesc VTDPRDesc; #define VTD_INV_DESC_PIOTLB_RSVD_VAL0 0xfff000000000f1c0ULL #define VTD_INV_DESC_PIOTLB_RSVD_VAL1 0xf80ULL +/* PASID-cache Invalidate Descriptor (pc_inv_dsc) fields */ +#define VTD_INV_DESC_PASIDC_G(x) extract64((x)->val[0], 4, 2) +#define VTD_INV_DESC_PASIDC_G_DSI 0 +#define VTD_INV_DESC_PASIDC_G_PASID_SI 1 +#define VTD_INV_DESC_PASIDC_G_GLOBAL 3 +#define VTD_INV_DESC_PASIDC_DID(x) extract64((x)->val[0], 16, 16) +#define VTD_INV_DESC_PASIDC_PASID(x) extract64((x)->val[0], 32, 20) +#define VTD_INV_DESC_PASIDC_RSVD_VAL0 0xfff000000000f1c0ULL + /* Page Request Descriptor */ /* For the low 64-bit of 128-bit */ #define VTD_PRD_TYPE (1ULL) @@ -603,6 +614,12 @@ typedef struct VTDRootEntry VTDRootEntry; #define VTD_SM_CONTEXT_ENTRY_RSVD_VAL1 0xffffffffffe00000ULL #define VTD_SM_CONTEXT_ENTRY_PRE 0x10ULL +typedef struct VTDPASIDCacheInfo { + uint8_t type; + uint16_t did; + uint32_t pasid; +} VTDPASIDCacheInfo; + /* PASID Table Related Definitions */ #define VTD_PASID_DIR_BASE_ADDR_MASK (~0xfffULL) #define VTD_PASID_TABLE_BASE_ADDR_MASK (~0xfffULL) diff --git a/hw/i386/trace-events b/hw/i386/trace-events index ac9e1a10aa..298addb24d 100644 --- a/hw/i386/trace-events +++ b/hw/i386/trace-events @@ -24,6 +24,9 @@ vtd_inv_qi_head(uint16_t head) "read head %d" vtd_inv_qi_tail(uint16_t head) "write tail %d" vtd_inv_qi_fetch(void) "" vtd_context_cache_reset(void) "" +vtd_inv_desc_pasid_cache_gsi(void) "" +vtd_inv_desc_pasid_cache_dsi(uint16_t domain) "Domain selective PC invalidation domain 0x%"PRIx16 +vtd_inv_desc_pasid_cache_psi(uint16_t domain, uint32_t pasid) "PASID selective PC invalidation domain 0x%"PRIx16" pasid 0x%"PRIx32 vtd_re_not_present(uint8_t bus) "Root entry bus %"PRIu8" not present" vtd_ce_not_present(uint8_t bus, uint8_t devfn) "Context entry bus %"PRIu8" devfn %"PRIu8" not present" vtd_iotlb_page_hit(uint16_t sid, uint64_t addr, uint64_t slpte, uint16_t domain) "IOTLB page hit sid 0x%"PRIx16" iova 0x%"PRIx64" slpte 0x%"PRIx64" domain 0x%"PRIx16 diff --git a/include/hw/i386/intel_iommu.h b/include/hw/i386/intel_iommu.h index b2f1ef9595..ca7f7bb661 100644 --- a/include/hw/i386/intel_iommu.h +++ b/include/hw/i386/intel_iommu.h @@ -95,6 +95,11 @@ struct VTDPASIDEntry { uint64_t val[8]; }; +typedef struct VTDPASIDCacheEntry { + struct VTDPASIDEntry pasid_entry; + bool valid; +} VTDPASIDCacheEntry; + struct VTDAddressSpace { PCIBus *bus; uint8_t devfn; @@ -107,6 +112,7 @@ struct VTDAddressSpace { MemoryRegion iommu_ir_fault; /* Interrupt region for catching fault */ IntelIOMMUState *iommu_state; VTDContextCacheEntry context_cache_entry; + VTDPASIDCacheEntry pasid_cache_entry; QLIST_ENTRY(VTDAddressSpace) next; /* Superset of notifier flags that this address space has */ IOMMUNotifierFlag notifier_flags;