18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Implementation of the IOMMU SVA API for the ARM SMMUv3
48c2ecf20Sopenharmony_ci */
58c2ecf20Sopenharmony_ci
68c2ecf20Sopenharmony_ci#include <linux/mm.h>
78c2ecf20Sopenharmony_ci#include <linux/mmu_context.h>
88c2ecf20Sopenharmony_ci#include <linux/slab.h>
98c2ecf20Sopenharmony_ci
108c2ecf20Sopenharmony_ci#include "arm-smmu-v3.h"
118c2ecf20Sopenharmony_ci#include "../../io-pgtable-arm.h"
128c2ecf20Sopenharmony_ci
138c2ecf20Sopenharmony_cistatic DEFINE_MUTEX(sva_lock);
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_ci/*
168c2ecf20Sopenharmony_ci * Check if the CPU ASID is available on the SMMU side. If a private context
178c2ecf20Sopenharmony_ci * descriptor is using it, try to replace it.
188c2ecf20Sopenharmony_ci */
198c2ecf20Sopenharmony_cistatic struct arm_smmu_ctx_desc *
208c2ecf20Sopenharmony_ciarm_smmu_share_asid(struct mm_struct *mm, u16 asid)
218c2ecf20Sopenharmony_ci{
228c2ecf20Sopenharmony_ci	int ret;
238c2ecf20Sopenharmony_ci	u32 new_asid;
248c2ecf20Sopenharmony_ci	struct arm_smmu_ctx_desc *cd;
258c2ecf20Sopenharmony_ci	struct arm_smmu_device *smmu;
268c2ecf20Sopenharmony_ci	struct arm_smmu_domain *smmu_domain;
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_ci	cd = xa_load(&arm_smmu_asid_xa, asid);
298c2ecf20Sopenharmony_ci	if (!cd)
308c2ecf20Sopenharmony_ci		return NULL;
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_ci	if (cd->mm) {
338c2ecf20Sopenharmony_ci		if (WARN_ON(cd->mm != mm))
348c2ecf20Sopenharmony_ci			return ERR_PTR(-EINVAL);
358c2ecf20Sopenharmony_ci		/* All devices bound to this mm use the same cd struct. */
368c2ecf20Sopenharmony_ci		refcount_inc(&cd->refs);
378c2ecf20Sopenharmony_ci		return cd;
388c2ecf20Sopenharmony_ci	}
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci	smmu_domain = container_of(cd, struct arm_smmu_domain, s1_cfg.cd);
418c2ecf20Sopenharmony_ci	smmu = smmu_domain->smmu;
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_ci	ret = xa_alloc(&arm_smmu_asid_xa, &new_asid, cd,
448c2ecf20Sopenharmony_ci		       XA_LIMIT(1, (1 << smmu->asid_bits) - 1), GFP_KERNEL);
458c2ecf20Sopenharmony_ci	if (ret)
468c2ecf20Sopenharmony_ci		return ERR_PTR(-ENOSPC);
478c2ecf20Sopenharmony_ci	/*
488c2ecf20Sopenharmony_ci	 * Race with unmap: TLB invalidations will start targeting the new ASID,
498c2ecf20Sopenharmony_ci	 * which isn't assigned yet. We'll do an invalidate-all on the old ASID
508c2ecf20Sopenharmony_ci	 * later, so it doesn't matter.
518c2ecf20Sopenharmony_ci	 */
528c2ecf20Sopenharmony_ci	cd->asid = new_asid;
538c2ecf20Sopenharmony_ci	/*
548c2ecf20Sopenharmony_ci	 * Update ASID and invalidate CD in all associated masters. There will
558c2ecf20Sopenharmony_ci	 * be some overlap between use of both ASIDs, until we invalidate the
568c2ecf20Sopenharmony_ci	 * TLB.
578c2ecf20Sopenharmony_ci	 */
588c2ecf20Sopenharmony_ci	arm_smmu_write_ctx_desc(smmu_domain, 0, cd);
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_ci	/* Invalidate TLB entries previously associated with that context */
618c2ecf20Sopenharmony_ci	arm_smmu_tlb_inv_asid(smmu, asid);
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_ci	xa_erase(&arm_smmu_asid_xa, asid);
648c2ecf20Sopenharmony_ci	return NULL;
658c2ecf20Sopenharmony_ci}
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_ci__maybe_unused
688c2ecf20Sopenharmony_cistatic struct arm_smmu_ctx_desc *arm_smmu_alloc_shared_cd(struct mm_struct *mm)
698c2ecf20Sopenharmony_ci{
708c2ecf20Sopenharmony_ci	u16 asid;
718c2ecf20Sopenharmony_ci	int err = 0;
728c2ecf20Sopenharmony_ci	u64 tcr, par, reg;
738c2ecf20Sopenharmony_ci	struct arm_smmu_ctx_desc *cd;
748c2ecf20Sopenharmony_ci	struct arm_smmu_ctx_desc *ret = NULL;
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_ci	asid = arm64_mm_context_get(mm);
778c2ecf20Sopenharmony_ci	if (!asid)
788c2ecf20Sopenharmony_ci		return ERR_PTR(-ESRCH);
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_ci	cd = kzalloc(sizeof(*cd), GFP_KERNEL);
818c2ecf20Sopenharmony_ci	if (!cd) {
828c2ecf20Sopenharmony_ci		err = -ENOMEM;
838c2ecf20Sopenharmony_ci		goto out_put_context;
848c2ecf20Sopenharmony_ci	}
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci	refcount_set(&cd->refs, 1);
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_ci	mutex_lock(&arm_smmu_asid_lock);
898c2ecf20Sopenharmony_ci	ret = arm_smmu_share_asid(mm, asid);
908c2ecf20Sopenharmony_ci	if (ret) {
918c2ecf20Sopenharmony_ci		mutex_unlock(&arm_smmu_asid_lock);
928c2ecf20Sopenharmony_ci		goto out_free_cd;
938c2ecf20Sopenharmony_ci	}
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_ci	err = xa_insert(&arm_smmu_asid_xa, asid, cd, GFP_KERNEL);
968c2ecf20Sopenharmony_ci	mutex_unlock(&arm_smmu_asid_lock);
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_ci	if (err)
998c2ecf20Sopenharmony_ci		goto out_free_asid;
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_ci	tcr = FIELD_PREP(CTXDESC_CD_0_TCR_T0SZ, 64ULL - vabits_actual) |
1028c2ecf20Sopenharmony_ci	      FIELD_PREP(CTXDESC_CD_0_TCR_IRGN0, ARM_LPAE_TCR_RGN_WBWA) |
1038c2ecf20Sopenharmony_ci	      FIELD_PREP(CTXDESC_CD_0_TCR_ORGN0, ARM_LPAE_TCR_RGN_WBWA) |
1048c2ecf20Sopenharmony_ci	      FIELD_PREP(CTXDESC_CD_0_TCR_SH0, ARM_LPAE_TCR_SH_IS) |
1058c2ecf20Sopenharmony_ci	      CTXDESC_CD_0_TCR_EPD1 | CTXDESC_CD_0_AA64;
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci	switch (PAGE_SIZE) {
1088c2ecf20Sopenharmony_ci	case SZ_4K:
1098c2ecf20Sopenharmony_ci		tcr |= FIELD_PREP(CTXDESC_CD_0_TCR_TG0, ARM_LPAE_TCR_TG0_4K);
1108c2ecf20Sopenharmony_ci		break;
1118c2ecf20Sopenharmony_ci	case SZ_16K:
1128c2ecf20Sopenharmony_ci		tcr |= FIELD_PREP(CTXDESC_CD_0_TCR_TG0, ARM_LPAE_TCR_TG0_16K);
1138c2ecf20Sopenharmony_ci		break;
1148c2ecf20Sopenharmony_ci	case SZ_64K:
1158c2ecf20Sopenharmony_ci		tcr |= FIELD_PREP(CTXDESC_CD_0_TCR_TG0, ARM_LPAE_TCR_TG0_64K);
1168c2ecf20Sopenharmony_ci		break;
1178c2ecf20Sopenharmony_ci	default:
1188c2ecf20Sopenharmony_ci		WARN_ON(1);
1198c2ecf20Sopenharmony_ci		err = -EINVAL;
1208c2ecf20Sopenharmony_ci		goto out_free_asid;
1218c2ecf20Sopenharmony_ci	}
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_ci	reg = read_sanitised_ftr_reg(SYS_ID_AA64MMFR0_EL1);
1248c2ecf20Sopenharmony_ci	par = cpuid_feature_extract_unsigned_field(reg, ID_AA64MMFR0_PARANGE_SHIFT);
1258c2ecf20Sopenharmony_ci	tcr |= FIELD_PREP(CTXDESC_CD_0_TCR_IPS, par);
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_ci	cd->ttbr = virt_to_phys(mm->pgd);
1288c2ecf20Sopenharmony_ci	cd->tcr = tcr;
1298c2ecf20Sopenharmony_ci	/*
1308c2ecf20Sopenharmony_ci	 * MAIR value is pretty much constant and global, so we can just get it
1318c2ecf20Sopenharmony_ci	 * from the current CPU register
1328c2ecf20Sopenharmony_ci	 */
1338c2ecf20Sopenharmony_ci	cd->mair = read_sysreg(mair_el1);
1348c2ecf20Sopenharmony_ci	cd->asid = asid;
1358c2ecf20Sopenharmony_ci	cd->mm = mm;
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci	return cd;
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_ciout_free_asid:
1408c2ecf20Sopenharmony_ci	arm_smmu_free_asid(cd);
1418c2ecf20Sopenharmony_ciout_free_cd:
1428c2ecf20Sopenharmony_ci	kfree(cd);
1438c2ecf20Sopenharmony_ciout_put_context:
1448c2ecf20Sopenharmony_ci	arm64_mm_context_put(mm);
1458c2ecf20Sopenharmony_ci	return err < 0 ? ERR_PTR(err) : ret;
1468c2ecf20Sopenharmony_ci}
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_ci__maybe_unused
1498c2ecf20Sopenharmony_cistatic void arm_smmu_free_shared_cd(struct arm_smmu_ctx_desc *cd)
1508c2ecf20Sopenharmony_ci{
1518c2ecf20Sopenharmony_ci	if (arm_smmu_free_asid(cd)) {
1528c2ecf20Sopenharmony_ci		/* Unpin ASID */
1538c2ecf20Sopenharmony_ci		arm64_mm_context_put(cd->mm);
1548c2ecf20Sopenharmony_ci		kfree(cd);
1558c2ecf20Sopenharmony_ci	}
1568c2ecf20Sopenharmony_ci}
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_cibool arm_smmu_sva_supported(struct arm_smmu_device *smmu)
1598c2ecf20Sopenharmony_ci{
1608c2ecf20Sopenharmony_ci	unsigned long reg, fld;
1618c2ecf20Sopenharmony_ci	unsigned long oas;
1628c2ecf20Sopenharmony_ci	unsigned long asid_bits;
1638c2ecf20Sopenharmony_ci	u32 feat_mask = ARM_SMMU_FEAT_BTM | ARM_SMMU_FEAT_COHERENCY;
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci	if (vabits_actual == 52)
1668c2ecf20Sopenharmony_ci		feat_mask |= ARM_SMMU_FEAT_VAX;
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_ci	if ((smmu->features & feat_mask) != feat_mask)
1698c2ecf20Sopenharmony_ci		return false;
1708c2ecf20Sopenharmony_ci
1718c2ecf20Sopenharmony_ci	if (!(smmu->pgsize_bitmap & PAGE_SIZE))
1728c2ecf20Sopenharmony_ci		return false;
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_ci	/*
1758c2ecf20Sopenharmony_ci	 * Get the smallest PA size of all CPUs (sanitized by cpufeature). We're
1768c2ecf20Sopenharmony_ci	 * not even pretending to support AArch32 here. Abort if the MMU outputs
1778c2ecf20Sopenharmony_ci	 * addresses larger than what we support.
1788c2ecf20Sopenharmony_ci	 */
1798c2ecf20Sopenharmony_ci	reg = read_sanitised_ftr_reg(SYS_ID_AA64MMFR0_EL1);
1808c2ecf20Sopenharmony_ci	fld = cpuid_feature_extract_unsigned_field(reg, ID_AA64MMFR0_PARANGE_SHIFT);
1818c2ecf20Sopenharmony_ci	oas = id_aa64mmfr0_parange_to_phys_shift(fld);
1828c2ecf20Sopenharmony_ci	if (smmu->oas < oas)
1838c2ecf20Sopenharmony_ci		return false;
1848c2ecf20Sopenharmony_ci
1858c2ecf20Sopenharmony_ci	/* We can support bigger ASIDs than the CPU, but not smaller */
1868c2ecf20Sopenharmony_ci	fld = cpuid_feature_extract_unsigned_field(reg, ID_AA64MMFR0_ASID_SHIFT);
1878c2ecf20Sopenharmony_ci	asid_bits = fld ? 16 : 8;
1888c2ecf20Sopenharmony_ci	if (smmu->asid_bits < asid_bits)
1898c2ecf20Sopenharmony_ci		return false;
1908c2ecf20Sopenharmony_ci
1918c2ecf20Sopenharmony_ci	/*
1928c2ecf20Sopenharmony_ci	 * See max_pinned_asids in arch/arm64/mm/context.c. The following is
1938c2ecf20Sopenharmony_ci	 * generally the maximum number of bindable processes.
1948c2ecf20Sopenharmony_ci	 */
1958c2ecf20Sopenharmony_ci	if (arm64_kernel_unmapped_at_el0())
1968c2ecf20Sopenharmony_ci		asid_bits--;
1978c2ecf20Sopenharmony_ci	dev_dbg(smmu->dev, "%d shared contexts\n", (1 << asid_bits) -
1988c2ecf20Sopenharmony_ci		num_possible_cpus() - 2);
1998c2ecf20Sopenharmony_ci
2008c2ecf20Sopenharmony_ci	return true;
2018c2ecf20Sopenharmony_ci}
2028c2ecf20Sopenharmony_ci
2038c2ecf20Sopenharmony_cistatic bool arm_smmu_iopf_supported(struct arm_smmu_master *master)
2048c2ecf20Sopenharmony_ci{
2058c2ecf20Sopenharmony_ci	return false;
2068c2ecf20Sopenharmony_ci}
2078c2ecf20Sopenharmony_ci
2088c2ecf20Sopenharmony_cibool arm_smmu_master_sva_supported(struct arm_smmu_master *master)
2098c2ecf20Sopenharmony_ci{
2108c2ecf20Sopenharmony_ci	if (!(master->smmu->features & ARM_SMMU_FEAT_SVA))
2118c2ecf20Sopenharmony_ci		return false;
2128c2ecf20Sopenharmony_ci
2138c2ecf20Sopenharmony_ci	/* SSID and IOPF support are mandatory for the moment */
2148c2ecf20Sopenharmony_ci	return master->ssid_bits && arm_smmu_iopf_supported(master);
2158c2ecf20Sopenharmony_ci}
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_cibool arm_smmu_master_sva_enabled(struct arm_smmu_master *master)
2188c2ecf20Sopenharmony_ci{
2198c2ecf20Sopenharmony_ci	bool enabled;
2208c2ecf20Sopenharmony_ci
2218c2ecf20Sopenharmony_ci	mutex_lock(&sva_lock);
2228c2ecf20Sopenharmony_ci	enabled = master->sva_enabled;
2238c2ecf20Sopenharmony_ci	mutex_unlock(&sva_lock);
2248c2ecf20Sopenharmony_ci	return enabled;
2258c2ecf20Sopenharmony_ci}
2268c2ecf20Sopenharmony_ci
2278c2ecf20Sopenharmony_ciint arm_smmu_master_enable_sva(struct arm_smmu_master *master)
2288c2ecf20Sopenharmony_ci{
2298c2ecf20Sopenharmony_ci	mutex_lock(&sva_lock);
2308c2ecf20Sopenharmony_ci	master->sva_enabled = true;
2318c2ecf20Sopenharmony_ci	mutex_unlock(&sva_lock);
2328c2ecf20Sopenharmony_ci
2338c2ecf20Sopenharmony_ci	return 0;
2348c2ecf20Sopenharmony_ci}
2358c2ecf20Sopenharmony_ci
2368c2ecf20Sopenharmony_ciint arm_smmu_master_disable_sva(struct arm_smmu_master *master)
2378c2ecf20Sopenharmony_ci{
2388c2ecf20Sopenharmony_ci	mutex_lock(&sva_lock);
2398c2ecf20Sopenharmony_ci	if (!list_empty(&master->bonds)) {
2408c2ecf20Sopenharmony_ci		dev_err(master->dev, "cannot disable SVA, device is bound\n");
2418c2ecf20Sopenharmony_ci		mutex_unlock(&sva_lock);
2428c2ecf20Sopenharmony_ci		return -EBUSY;
2438c2ecf20Sopenharmony_ci	}
2448c2ecf20Sopenharmony_ci	master->sva_enabled = false;
2458c2ecf20Sopenharmony_ci	mutex_unlock(&sva_lock);
2468c2ecf20Sopenharmony_ci
2478c2ecf20Sopenharmony_ci	return 0;
2488c2ecf20Sopenharmony_ci}
249