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

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.com


Signed-off-by: default avatarJoerg Roedel <jroedel@suse.de>
parent 24f27d32
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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),
+85 −0
Original line number Diff line number Diff line
@@ -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 @@ out_unmap:
	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);
+7 −0
Original line number Diff line number Diff line
@@ -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.*/