Commit 3375303e authored by Jacob Pan's avatar Jacob Pan Committed by Joerg Roedel

iommu/vt-d: Add custom allocator for IOASID

When VT-d driver runs in the guest, PASID allocation must be
performed via virtual command interface. This patch registers a
custom IOASID allocator which takes precedence over the default
XArray based allocator. The resulting IOASID allocation will always
come from the host. This ensures that PASID namespace is system-
wide.

Virtual command registers are used in the guest only, to prevent
vmexit cost, we cache the capability and store it during initialization.
Signed-off-by: default avatarLiu, Yi L <yi.l.liu@intel.com>
Signed-off-by: default avatarJacob Pan <jacob.jun.pan@linux.intel.com>
Signed-off-by: default avatarLu Baolu <baolu.lu@linux.intel.com>
Reviewed-by: default avatarEric Auger <eric.auger@redhat.com>
Link: https://lore.kernel.org/r/20200516062101.29541-9-baolu.lu@linux.intel.comSigned-off-by: default avatarJoerg Roedel <jroedel@suse.de>
parent 24f27d32
......@@ -963,6 +963,7 @@ static int map_iommu(struct intel_iommu *iommu, u64 phys_addr)
warn_invalid_dmar(phys_addr, " returns all ones");
goto unmap;
}
iommu->vccap = dmar_readq(iommu->reg + DMAR_VCCAP_REG);
/* the registers might be more than one page */
map_size = max_t(int, ecap_max_iotlb_offset(iommu->ecap),
......
......@@ -1726,6 +1726,9 @@ static void free_dmar_iommu(struct intel_iommu *iommu)
if (ecap_prs(iommu->ecap))
intel_svm_finish_prq(iommu);
}
if (ecap_vcs(iommu->ecap) && vccap_pasid(iommu->vccap))
ioasid_unregister_allocator(&iommu->pasid_allocator);
#endif
}
......@@ -3038,6 +3041,85 @@ static int copy_translation_tables(struct intel_iommu *iommu)
return ret;
}
#ifdef CONFIG_INTEL_IOMMU_SVM
static ioasid_t intel_vcmd_ioasid_alloc(ioasid_t min, ioasid_t max, void *data)
{
struct intel_iommu *iommu = data;
ioasid_t ioasid;
if (!iommu)
return INVALID_IOASID;
/*
* VT-d virtual command interface always uses the full 20 bit
* PASID range. Host can partition guest PASID range based on
* policies but it is out of guest's control.
*/
if (min < PASID_MIN || max > intel_pasid_max_id)
return INVALID_IOASID;
if (vcmd_alloc_pasid(iommu, &ioasid))
return INVALID_IOASID;
return ioasid;
}
static void intel_vcmd_ioasid_free(ioasid_t ioasid, void *data)
{
struct intel_iommu *iommu = data;
if (!iommu)
return;
/*
* Sanity check the ioasid owner is done at upper layer, e.g. VFIO
* We can only free the PASID when all the devices are unbound.
*/
if (ioasid_find(NULL, ioasid, NULL)) {
pr_alert("Cannot free active IOASID %d\n", ioasid);
return;
}
vcmd_free_pasid(iommu, ioasid);
}
static void register_pasid_allocator(struct intel_iommu *iommu)
{
/*
* If we are running in the host, no need for custom allocator
* in that PASIDs are allocated from the host system-wide.
*/
if (!cap_caching_mode(iommu->cap))
return;
if (!sm_supported(iommu)) {
pr_warn("VT-d Scalable Mode not enabled, no PASID allocation\n");
return;
}
/*
* Register a custom PASID allocator if we are running in a guest,
* guest PASID must be obtained via virtual command interface.
* There can be multiple vIOMMUs in each guest but only one allocator
* is active. All vIOMMU allocators will eventually be calling the same
* host allocator.
*/
if (!ecap_vcs(iommu->ecap) || !vccap_pasid(iommu->vccap))
return;
pr_info("Register custom PASID allocator\n");
iommu->pasid_allocator.alloc = intel_vcmd_ioasid_alloc;
iommu->pasid_allocator.free = intel_vcmd_ioasid_free;
iommu->pasid_allocator.pdata = (void *)iommu;
if (ioasid_register_allocator(&iommu->pasid_allocator)) {
pr_warn("Custom PASID allocator failed, scalable mode disabled\n");
/*
* Disable scalable mode on this IOMMU if there
* is no custom allocator. Mixing SM capable vIOMMU
* and non-SM vIOMMU are not supported.
*/
intel_iommu_sm = 0;
}
}
#endif
static int __init init_dmars(void)
{
struct dmar_drhd_unit *drhd;
......@@ -3155,6 +3237,9 @@ static int __init init_dmars(void)
*/
for_each_active_iommu(iommu, drhd) {
iommu_flush_write_buffer(iommu);
#ifdef CONFIG_INTEL_IOMMU_SVM
register_pasid_allocator(iommu);
#endif
iommu_set_root_entry(iommu);
iommu->flush.flush_context(iommu, 0, 0, 0, DMA_CCMD_GLOBAL_INVL);
iommu->flush.flush_iotlb(iommu, 0, 0, 0, DMA_TLB_GLOBAL_FLUSH);
......
......@@ -19,6 +19,7 @@
#include <linux/iommu.h>
#include <linux/io-64-nonatomic-lo-hi.h>
#include <linux/dmar.h>
#include <linux/ioasid.h>
#include <asm/cacheflush.h>
#include <asm/iommu.h>
......@@ -195,6 +196,9 @@
#define ecap_max_handle_mask(e) ((e >> 20) & 0xf)
#define ecap_sc_support(e) ((e >> 7) & 0x1) /* Snooping Control */
/* Virtual command interface capability */
#define vccap_pasid(v) (((v) & DMA_VCS_PAS)) /* PASID allocation */
/* IOTLB_REG */
#define DMA_TLB_FLUSH_GRANU_OFFSET 60
#define DMA_TLB_GLOBAL_FLUSH (((u64)1) << 60)
......@@ -288,6 +292,7 @@
/* PRS_REG */
#define DMA_PRS_PPR ((u32)1)
#define DMA_VCS_PAS ((u64)1)
#define IOMMU_WAIT_OP(iommu, offset, op, cond, sts) \
do { \
......@@ -555,6 +560,7 @@ struct intel_iommu {
u64 reg_size; /* size of hw register set */
u64 cap;
u64 ecap;
u64 vccap;
u32 gcmd; /* Holds TE, EAFL. Don't need SRTP, SFL, WBF */
raw_spinlock_t register_lock; /* protect register handling */
int seq_id; /* sequence id of the iommu */
......@@ -575,6 +581,7 @@ struct intel_iommu {
#ifdef CONFIG_INTEL_IOMMU_SVM
struct page_req_dsc *prq;
unsigned char prq_name[16]; /* Name for PRQ interrupt */
struct ioasid_allocator_ops pasid_allocator; /* Custom allocator for PASIDs */
#endif
struct q_inval *qi; /* Queued invalidation info */
u32 *iommu_state; /* Store iommu states between suspend and resume.*/
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment