Commit a98db518 authored by Lu Baolu's avatar Lu Baolu Committed by Joerg Roedel
Browse files

iommu/vt-d: Enhance compatibility check for paging domain attach



The driver now supports domain_alloc_paging, ensuring that a valid device
pointer is provided whenever a paging domain is allocated. Additionally,
the dmar_domain attributes are set up at the time of allocation.

Consistent with the established semantics in the IOMMU core, if a domain is
attached to a device and found to be incompatible with the IOMMU hardware
capabilities, the operation will return an -EINVAL error. This implicitly
advises the caller to allocate a new domain for the device and attempt the
domain attachment again.

Rename prepare_domain_attach_device() to a more meaningful name.

Signed-off-by: default avatarLu Baolu <baolu.lu@linux.intel.com>
Reviewed-by: default avatarJason Gunthorpe <jgg@nvidia.com>
Link: https://lore.kernel.org/r/20241021085125.192333-4-baolu.lu@linux.intel.com


Signed-off-by: default avatarJoerg Roedel <jroedel@suse.de>
parent 9ecfcac1
Loading
Loading
Loading
Loading
+23 −47
Original line number Diff line number Diff line
@@ -1606,7 +1606,7 @@ static int domain_context_mapping_one(struct dmar_domain *domain,
	int translation = CONTEXT_TT_MULTI_LEVEL;
	struct dma_pte *pgd = domain->pgd;
	struct context_entry *context;
	int agaw, ret;
	int ret;

	pr_debug("Set context mapping for %02x:%02x.%d\n",
		bus, PCI_SLOT(devfn), PCI_FUNC(devfn));
@@ -1623,27 +1623,15 @@ static int domain_context_mapping_one(struct dmar_domain *domain,

	copied_context_tear_down(iommu, context, bus, devfn);
	context_clear_entry(context);

	context_set_domain_id(context, did);

	/*
	 * Skip top levels of page tables for iommu which has
	 * less agaw than default. Unnecessary for PT mode.
	 */
	for (agaw = domain->agaw; agaw > iommu->agaw; agaw--) {
		ret = -ENOMEM;
		pgd = phys_to_virt(dma_pte_addr(pgd));
		if (!dma_pte_present(pgd))
			goto out_unlock;
	}

	if (info && info->ats_supported)
		translation = CONTEXT_TT_DEV_IOTLB;
	else
		translation = CONTEXT_TT_MULTI_LEVEL;

	context_set_address_root(context, virt_to_phys(pgd));
	context_set_address_width(context, agaw);
	context_set_address_width(context, domain->agaw);
	context_set_translation_type(context, translation);
	context_set_fault_enable(context);
	context_set_present(context);
@@ -1876,20 +1864,9 @@ static int domain_setup_first_level(struct intel_iommu *iommu,
				    u32 pasid)
{
	struct dma_pte *pgd = domain->pgd;
	int agaw, level;
	int flags = 0;
	int level, flags = 0;

	/*
	 * Skip top levels of page tables for iommu which has
	 * less agaw than default. Unnecessary for PT mode.
	 */
	for (agaw = domain->agaw; agaw > iommu->agaw; agaw--) {
		pgd = phys_to_virt(dma_pte_addr(pgd));
		if (!dma_pte_present(pgd))
			return -ENOMEM;
	}

	level = agaw_to_level(agaw);
	level = agaw_to_level(domain->agaw);
	if (level != 4 && level != 5)
		return -EINVAL;

@@ -3492,42 +3469,41 @@ static void intel_iommu_domain_free(struct iommu_domain *domain)
	domain_exit(dmar_domain);
}

int prepare_domain_attach_device(struct iommu_domain *domain,
				 struct device *dev)
int paging_domain_compatible(struct iommu_domain *domain, struct device *dev)
{
	struct device_domain_info *info = dev_iommu_priv_get(dev);
	struct dmar_domain *dmar_domain = to_dmar_domain(domain);
	struct intel_iommu *iommu = info->iommu;
	int addr_width;

	if (WARN_ON_ONCE(!(domain->type & __IOMMU_DOMAIN_PAGING)))
		return -EPERM;

	if (dmar_domain->force_snooping && !ecap_sc_support(iommu->ecap))
		return -EINVAL;

	if (domain->dirty_ops && !ssads_supported(iommu))
		return -EINVAL;

	if (dmar_domain->iommu_coherency !=
			iommu_paging_structure_coherency(iommu))
		return -EINVAL;

	if (dmar_domain->iommu_superpage !=
			iommu_superpage_capability(iommu, dmar_domain->use_first_level))
		return -EINVAL;

	if (dmar_domain->use_first_level &&
	    (!sm_supported(iommu) || !ecap_flts(iommu->ecap)))
		return -EINVAL;

	/* check if this iommu agaw is sufficient for max mapped address */
	addr_width = agaw_to_width(iommu->agaw);
	if (addr_width > cap_mgaw(iommu->cap))
		addr_width = cap_mgaw(iommu->cap);

	if (dmar_domain->max_addr > (1LL << addr_width))
	if (dmar_domain->gaw > addr_width || dmar_domain->agaw > iommu->agaw)
		return -EINVAL;
	dmar_domain->gaw = addr_width;

	/*
	 * Knock out extra levels of page tables if necessary
	 */
	while (iommu->agaw < dmar_domain->agaw) {
		struct dma_pte *pte;

		pte = dmar_domain->pgd;
		if (dma_pte_present(pte)) {
			dmar_domain->pgd = phys_to_virt(dma_pte_addr(pte));
			iommu_free_page(pte);
		}
		dmar_domain->agaw--;
	}

	if (sm_supported(iommu) && !dev_is_real_dma_subdevice(dev) &&
	    context_copied(iommu, info->bus, info->devfn))
@@ -3543,7 +3519,7 @@ static int intel_iommu_attach_device(struct iommu_domain *domain,

	device_block_translation(dev);

	ret = prepare_domain_attach_device(domain, dev);
	ret = paging_domain_compatible(domain, dev);
	if (ret)
		return ret;

@@ -4214,7 +4190,7 @@ static int intel_iommu_set_dev_pasid(struct iommu_domain *domain,
	if (context_copied(iommu, info->bus, info->devfn))
		return -EBUSY;

	ret = prepare_domain_attach_device(domain, dev);
	ret = paging_domain_compatible(domain, dev);
	if (ret)
		return ret;

+1 −2
Original line number Diff line number Diff line
@@ -1230,8 +1230,7 @@ void __iommu_flush_iotlb(struct intel_iommu *iommu, u16 did, u64 addr,
int domain_attach_iommu(struct dmar_domain *domain, struct intel_iommu *iommu);
void domain_detach_iommu(struct dmar_domain *domain, struct intel_iommu *iommu);
void device_block_translation(struct device *dev);
int prepare_domain_attach_device(struct iommu_domain *domain,
				 struct device *dev);
int paging_domain_compatible(struct iommu_domain *domain, struct device *dev);
void domain_update_iommu_cap(struct dmar_domain *domain);

int dmar_ir_support(void);
+1 −1
Original line number Diff line number Diff line
@@ -40,7 +40,7 @@ static int intel_nested_attach_dev(struct iommu_domain *domain,
	 * The s2_domain will be used in nested translation, hence needs
	 * to ensure the s2_domain is compatible with this IOMMU.
	 */
	ret = prepare_domain_attach_device(&dmar_domain->s2_domain->domain, dev);
	ret = paging_domain_compatible(&dmar_domain->s2_domain->domain, dev);
	if (ret) {
		dev_err_ratelimited(dev, "s2 domain is not compatible\n");
		return ret;
+1 −27
Original line number Diff line number Diff line
@@ -345,25 +345,6 @@ int intel_pasid_setup_first_level(struct intel_iommu *iommu,
	return 0;
}

/*
 * Skip top levels of page tables for iommu which has less agaw
 * than default. Unnecessary for PT mode.
 */
static int iommu_skip_agaw(struct dmar_domain *domain,
			   struct intel_iommu *iommu,
			   struct dma_pte **pgd)
{
	int agaw;

	for (agaw = domain->agaw; agaw > iommu->agaw; agaw--) {
		*pgd = phys_to_virt(dma_pte_addr(*pgd));
		if (!dma_pte_present(*pgd))
			return -EINVAL;
	}

	return agaw;
}

/*
 * Set up the scalable mode pasid entry for second only translation type.
 */
@@ -374,7 +355,6 @@ int intel_pasid_setup_second_level(struct intel_iommu *iommu,
	struct pasid_entry *pte;
	struct dma_pte *pgd;
	u64 pgd_val;
	int agaw;
	u16 did;

	/*
@@ -388,12 +368,6 @@ int intel_pasid_setup_second_level(struct intel_iommu *iommu,
	}

	pgd = domain->pgd;
	agaw = iommu_skip_agaw(domain, iommu, &pgd);
	if (agaw < 0) {
		dev_err(dev, "Invalid domain page table\n");
		return -EINVAL;
	}

	pgd_val = virt_to_phys(pgd);
	did = domain_id_iommu(domain, iommu);

@@ -412,7 +386,7 @@ int intel_pasid_setup_second_level(struct intel_iommu *iommu,
	pasid_clear_entry(pte);
	pasid_set_domain_id(pte, did);
	pasid_set_slptr(pte, pgd_val);
	pasid_set_address_width(pte, agaw);
	pasid_set_address_width(pte, domain->agaw);
	pasid_set_translation_type(pte, PASID_ENTRY_PGTT_SL_ONLY);
	pasid_set_fault_enable(pte);
	pasid_set_page_snoop(pte, !!ecap_smpwc(iommu->ecap));