提交 7ca84842 编写于 作者: J Jean-Philippe Brucker 提交者: Zheng Zengkai

iommu/arm-smmu-v3: Support auxiliary domains

maillist inclusion
category: feature
bugzilla: 51855
CVE: NA

Reference: https://jpbrucker.net/git/linux/commit/?h=sva/2021-03-01&id=4411467daeff90c7c371cef6369e4bf8561fb00e

---------------------------------------------

In commit a3a19592 ("iommu: Add APIs for multiple domains per
device"), the IOMMU API gained the concept of auxiliary domains (AUXD),
which allows to control the PASID-tagged address spaces of a device. With
AUXD the PASID address space are not shared with the CPU, but are instead
modified with iommu_map() and iommu_unmap() calls on auxiliary domains.

Add auxiliary domain support to the SMMUv3 driver. Device drivers allocate
an unmanaged IOMMU domain with iommu_domain_alloc(), and attach it to the
device with iommu_aux_attach_domain().

The AUXD API is fairly permissive, and allows to attach an IOMMU domain in
both normal and auxiliary mode at the same time - one device can be
attached to the domain normally, and another device can be attached
through one of its PASIDs. To avoid excessive complexity in the SMMU
implementation we pose some restrictions on supported AUXD usage:

* A domain is either in auxiliary mode or normal mode. And that state is
  sticky. Once detached the domain has to be re-attached in the same mode.

* An auxiliary domain can have a single parent domain. Two devices can be
  attached to the same auxiliary domain only if they are attached to the
  same parent domain.

In practice these shouldn't be problematic, since we have the same kind of
restriction on normal domains and users have been able to cope so far: at
the moment a domain cannot be attached to two devices behind different
SMMUs. When VFIO puts two such devices in the same container, it simply
falls back to allocating two separate IOMMU domains.

Be careful with mixing ATS and PASID. PCIe does not provide a way to only
invalidate non-PASID ATC entries, without also invalidating all
PASID-tagged ATC entries in the same address range! Try to avoid using
PASID and non-PASID contexts at the same time.

FIXME: if a device is removed from the domain while we drop an auxiliary
domain, there may be a problem. We remove the ctx desc, invalidate CD
cache for all devices, then invaliate the ATC for all devices. But we
drop the devices lock between the two invalidations so if a device is
removed we might miss the ATC inval?
Signed-off-by: NJean-Philippe Brucker <jean-philippe@linaro.org>
Signed-off-by: NLijun Fang <fanglijun3@huawei.com>
Reviewed-by: NWeilong Chen <chenweilong@huawei.com>
Signed-off-by: NZheng Zengkai <zhengzengkai@huawei.com>
上级 5d84ca80
......@@ -303,6 +303,7 @@ config ARM_SMMU_DISABLE_BYPASS_BY_DEFAULT
config ARM_SMMU_V3
tristate "ARM Ltd. System MMU Version 3 (SMMUv3) Support"
depends on ARM64
select IOASID
select IOMMU_API
select IOMMU_IO_PGTABLE_LPAE
select GENERIC_MSI_IRQ_DOMAIN
......
......@@ -18,6 +18,7 @@
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/io-pgtable.h>
#include <linux/ioasid.h>
#include <linux/iopoll.h>
#include <linux/module.h>
#include <linux/msi.h>
......@@ -76,6 +77,7 @@ struct arm_smmu_option_prop {
DEFINE_XARRAY_ALLOC1(arm_smmu_asid_xa);
DEFINE_MUTEX(arm_smmu_asid_lock);
static DECLARE_IOASID_SET(private_ioasid);
/*
* Special value used by SVA when a process dies, to quiesce a CD without
......@@ -1173,6 +1175,9 @@ static void arm_smmu_free_cd_tables(struct arm_smmu_domain *smmu_domain)
struct arm_smmu_device *smmu = smmu_domain->smmu;
struct arm_smmu_ctx_desc_cfg *cdcfg = &smmu_domain->s1_cfg.cdcfg;
if (!cdcfg->cdtab)
return;
if (cdcfg->l1_desc) {
size = CTXDESC_L2_ENTRIES * (CTXDESC_CD_DWORDS << 3);
......@@ -1867,12 +1872,12 @@ arm_smmu_atc_inv_to_cmd(int ssid, unsigned long iova, size_t size,
cmd->atc.size = log2_span;
}
static int arm_smmu_atc_inv_master(struct arm_smmu_master *master)
static int arm_smmu_atc_inv_master(struct arm_smmu_master *master, unsigned int ssid)
{
int i;
struct arm_smmu_cmdq_ent cmd;
arm_smmu_atc_inv_to_cmd(0, 0, 0, &cmd);
arm_smmu_atc_inv_to_cmd(ssid, 0, 0, &cmd);
for (i = 0; i < master->num_streams; i++) {
cmd.atc.sid = master->streams[i].id;
......@@ -1950,7 +1955,12 @@ static void arm_smmu_tlb_inv_context(void *cookie)
arm_smmu_cmdq_issue_cmd(smmu, &cmd);
arm_smmu_cmdq_issue_sync(smmu);
}
arm_smmu_atc_inv_domain(smmu_domain, 0, 0, 0);
if (smmu_domain->parent)
arm_smmu_atc_inv_domain(smmu_domain->parent, smmu_domain->ssid,
0, 0);
else
arm_smmu_atc_inv_domain(smmu_domain, 0, 0, 0);
}
static void __arm_smmu_tlb_inv_range(struct arm_smmu_cmdq_ent *cmd,
......@@ -2036,7 +2046,11 @@ static void arm_smmu_tlb_inv_range_domain(unsigned long iova, size_t size,
* Unfortunately, this can't be leaf-only since we may have
* zapped an entire table.
*/
arm_smmu_atc_inv_domain(smmu_domain, 0, iova, size);
if (smmu_domain->parent)
arm_smmu_atc_inv_domain(smmu_domain->parent, smmu_domain->ssid,
iova, size);
else
arm_smmu_atc_inv_domain(smmu_domain, 0, iova, size);
}
void arm_smmu_tlb_inv_range_asid(unsigned long iova, size_t size, int asid,
......@@ -2158,6 +2172,8 @@ static void arm_smmu_domain_free(struct iommu_domain *domain)
arm_smmu_free_cd_tables(smmu_domain);
arm_smmu_free_asid(&cfg->cd);
mutex_unlock(&arm_smmu_asid_lock);
if (smmu_domain->ssid)
ioasid_put(smmu_domain->ssid);
} else {
struct arm_smmu_s2_cfg *cfg = &smmu_domain->s2_cfg;
if (cfg->vmid)
......@@ -2167,7 +2183,7 @@ static void arm_smmu_domain_free(struct iommu_domain *domain)
kfree(smmu_domain);
}
static int arm_smmu_domain_finalise_s1(struct arm_smmu_domain *smmu_domain,
static int arm_smmu_domain_finalise_cd(struct arm_smmu_domain *smmu_domain,
struct arm_smmu_master *master,
struct io_pgtable_cfg *pgtbl_cfg)
{
......@@ -2179,20 +2195,10 @@ static int arm_smmu_domain_finalise_s1(struct arm_smmu_domain *smmu_domain,
refcount_set(&cfg->cd.refs, 1);
/* Prevent SVA from modifying the ASID until it is written to the CD */
mutex_lock(&arm_smmu_asid_lock);
ret = xa_alloc(&arm_smmu_asid_xa, &asid, &cfg->cd,
XA_LIMIT(1, (1 << smmu->asid_bits) - 1), GFP_KERNEL);
if (ret)
goto out_unlock;
cfg->s1cdmax = master->ssid_bits;
smmu_domain->stall_enabled = master->stall_enabled;
ret = arm_smmu_alloc_cd_tables(smmu_domain);
if (ret)
goto out_free_asid;
return ret;
cfg->cd.asid = (u16)asid;
cfg->cd.ttbr = pgtbl_cfg->arm_lpae_s1_cfg.ttbr;
......@@ -2204,6 +2210,29 @@ static int arm_smmu_domain_finalise_s1(struct arm_smmu_domain *smmu_domain,
FIELD_PREP(CTXDESC_CD_0_TCR_IPS, tcr->ips) |
CTXDESC_CD_0_TCR_EPD1 | CTXDESC_CD_0_AA64;
cfg->cd.mair = pgtbl_cfg->arm_lpae_s1_cfg.mair;
return 0;
}
static int arm_smmu_domain_finalise_s1(struct arm_smmu_domain *smmu_domain,
struct arm_smmu_master *master,
struct io_pgtable_cfg *pgtbl_cfg)
{
int ret;
struct arm_smmu_s1_cfg *cfg = &smmu_domain->s1_cfg;
/* Prevent SVA from modifying the ASID until it is written to the CD */
mutex_lock(&arm_smmu_asid_lock);
ret = arm_smmu_domain_finalise_cd(smmu_domain, master, pgtbl_cfg);
if (ret)
goto out_unlock;
cfg->s1cdmax = master->ssid_bits;
smmu_domain->stall_enabled = master->stall_enabled;
ret = arm_smmu_alloc_cd_tables(smmu_domain);
if (ret)
goto out_free_asid;
/*
* Note that this will end up calling arm_smmu_sync_cd() before
......@@ -2283,7 +2312,10 @@ static int arm_smmu_domain_finalise(struct iommu_domain *domain,
ias = min_t(unsigned long, ias, VA_BITS);
oas = smmu->ias;
fmt = ARM_64_LPAE_S1;
finalise_stage_fn = arm_smmu_domain_finalise_s1;
if (smmu_domain->parent)
finalise_stage_fn = arm_smmu_domain_finalise_cd;
else
finalise_stage_fn = arm_smmu_domain_finalise_s1;
break;
case ARM_SMMU_DOMAIN_NESTED:
case ARM_SMMU_DOMAIN_S2:
......@@ -2417,7 +2449,7 @@ static void arm_smmu_disable_ats(struct arm_smmu_master *master)
* ATC invalidation via the SMMU.
*/
wmb();
arm_smmu_atc_inv_master(master);
arm_smmu_atc_inv_master(master, 0);
atomic_dec(&smmu_domain->nr_ats_masters);
}
......@@ -2610,6 +2642,10 @@ static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev)
smmu_domain->stall_enabled ? "enabled" : "disabled");
ret = -EINVAL;
goto out_unlock;
} else if (smmu_domain->parent) {
dev_err(dev, "cannot attach auxiliary domain\n");
ret = -EINVAL;
goto out_unlock;
}
master->domain = smmu_domain;
......@@ -3003,6 +3039,8 @@ static bool arm_smmu_dev_has_feature(struct device *dev,
return arm_smmu_master_iopf_supported(master);
case IOMMU_DEV_FEAT_SVA:
return arm_smmu_master_sva_supported(master);
case IOMMU_DEV_FEAT_AUX:
return master->ssid_bits != 0;
default:
return false;
}
......@@ -3021,6 +3059,8 @@ static bool arm_smmu_dev_feature_enabled(struct device *dev,
return master->iopf_enabled;
case IOMMU_DEV_FEAT_SVA:
return arm_smmu_master_sva_enabled(master);
case IOMMU_DEV_FEAT_AUX:
return master->auxd_enabled;
default:
return false;
}
......@@ -3042,6 +3082,9 @@ static int arm_smmu_dev_enable_feature(struct device *dev,
return arm_smmu_master_enable_iopf(master);
case IOMMU_DEV_FEAT_SVA:
return arm_smmu_master_enable_sva(master);
case IOMMU_DEV_FEAT_AUX:
master->auxd_enabled = true;
return 0;
default:
return -EINVAL;
}
......@@ -3060,11 +3103,130 @@ static int arm_smmu_dev_disable_feature(struct device *dev,
return arm_smmu_master_disable_iopf(master);
case IOMMU_DEV_FEAT_SVA:
return arm_smmu_master_disable_sva(master);
case IOMMU_DEV_FEAT_AUX:
/* TODO: check if aux domains are still attached? */
master->auxd_enabled = false;
return 0;
default:
return -EINVAL;
}
}
static int arm_smmu_aux_attach_dev(struct iommu_domain *domain, struct device *dev)
{
int ret;
struct iommu_domain *parent_domain;
struct arm_smmu_domain *parent_smmu_domain;
struct arm_smmu_master *master = dev_iommu_priv_get(dev);
struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain);
if (!arm_smmu_dev_feature_enabled(dev, IOMMU_DEV_FEAT_AUX))
return -EINVAL;
parent_domain = iommu_get_domain_for_dev(dev);
if (!parent_domain)
return -EINVAL;
parent_smmu_domain = to_smmu_domain(parent_domain);
mutex_lock(&smmu_domain->init_mutex);
if (smmu_domain->stage != ARM_SMMU_DOMAIN_S1 ||
parent_smmu_domain->stage != ARM_SMMU_DOMAIN_S1) {
ret = -EINVAL;
goto out_unlock;
} else if (smmu_domain->s1_cfg.cdcfg.cdtab) {
/* Already attached as a normal domain */
dev_err(dev, "cannot attach domain in auxiliary mode\n");
ret = -EINVAL;
goto out_unlock;
} else if (!smmu_domain->smmu) {
ioasid_t ssid = ioasid_alloc(&private_ioasid, 1,
(1UL << master->ssid_bits) - 1,
NULL);
if (ssid == INVALID_IOASID) {
ret = -EINVAL;
goto out_unlock;
}
smmu_domain->smmu = master->smmu;
smmu_domain->parent = parent_smmu_domain;
smmu_domain->ssid = ssid;
ret = arm_smmu_domain_finalise(domain, master);
if (ret) {
smmu_domain->smmu = NULL;
smmu_domain->ssid = 0;
smmu_domain->parent = NULL;
ioasid_put(ssid);
goto out_unlock;
}
} else if (smmu_domain->parent != parent_smmu_domain) {
/* Additional restriction: an aux domain has a single parent */
dev_err(dev, "cannot attach aux domain with different parent\n");
ret = -EINVAL;
goto out_unlock;
}
/* FIXME: serialize against arm_smmu_share_asid() */
if (!smmu_domain->aux_nr_devs++)
arm_smmu_write_ctx_desc(parent_smmu_domain, smmu_domain->ssid,
&smmu_domain->s1_cfg.cd);
/*
* Note that all other devices attached to the parent domain can now
* access this context as well.
*/
out_unlock:
mutex_unlock(&smmu_domain->init_mutex);
return ret;
}
static void arm_smmu_aux_detach_dev(struct iommu_domain *domain, struct device *dev)
{
struct iommu_domain *parent_domain;
struct arm_smmu_domain *parent_smmu_domain;
struct arm_smmu_master *master = dev_iommu_priv_get(dev);
struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain);
if (!arm_smmu_dev_feature_enabled(dev, IOMMU_DEV_FEAT_AUX))
return;
parent_domain = iommu_get_domain_for_dev(dev);
if (!parent_domain)
return;
parent_smmu_domain = to_smmu_domain(parent_domain);
mutex_lock(&smmu_domain->init_mutex);
if (!smmu_domain->aux_nr_devs)
goto out_unlock;
if (!--smmu_domain->aux_nr_devs) {
arm_smmu_write_ctx_desc(parent_smmu_domain, smmu_domain->ssid,
NULL);
/*
* TLB doesn't need invalidation since accesses from the device
* can't use this domain's ASID once the CD is clear.
*
* Sadly that doesn't apply to ATCs, which are PASID tagged.
* Invalidate all other devices as well, because even though
* they weren't 'officially' attached to the auxiliary domain,
* they could have formed ATC entries.
*/
arm_smmu_atc_inv_domain(parent_smmu_domain, smmu_domain->ssid,
0, 0);
} else {
/* Invalidate only this device's ATC */
arm_smmu_atc_inv_master(master, smmu_domain->ssid);
}
out_unlock:
mutex_unlock(&smmu_domain->init_mutex);
}
static int arm_smmu_aux_get_pasid(struct iommu_domain *domain, struct device *dev)
{
struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain);
return smmu_domain->ssid ?: -EINVAL;
}
static struct iommu_ops arm_smmu_ops = {
.capable = arm_smmu_capable,
.domain_alloc = arm_smmu_domain_alloc,
......@@ -3091,6 +3253,9 @@ static struct iommu_ops arm_smmu_ops = {
.sva_unbind = arm_smmu_sva_unbind,
.sva_get_pasid = arm_smmu_sva_get_pasid,
.page_response = arm_smmu_page_response,
.aux_attach_dev = arm_smmu_aux_attach_dev,
.aux_detach_dev = arm_smmu_aux_detach_dev,
.aux_get_pasid = arm_smmu_aux_get_pasid,
.pgsize_bitmap = -1UL, /* Restricted during device attach */
};
......
......@@ -708,6 +708,7 @@ struct arm_smmu_master {
bool prg_resp_needs_ssid;
bool sva_enabled;
bool iopf_enabled;
bool auxd_enabled;
struct list_head bonds;
unsigned int ssid_bits;
};
......@@ -737,10 +738,16 @@ struct arm_smmu_domain {
struct iommu_domain domain;
/* Unused in aux domains */
struct list_head devices;
spinlock_t devices_lock;
struct list_head mmu_notifiers;
/* Auxiliary domain stuff */
struct arm_smmu_domain *parent;
ioasid_t ssid;
unsigned long aux_nr_devs;
};
static inline struct arm_smmu_domain *to_smmu_domain(struct iommu_domain *dom)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册