Allow device driver to create PASID contexts by setting a device in
'auxiliary' mode and allocating new domains. Each domain corrsponds to a
PASID.

Signed-off-by: Jean-Philippe Brucker <jean-philippe.bruc...@arm.com>
---
 drivers/iommu/arm-smmu-v3.c | 157 ++++++++++++++++++++++++++++++++++--
 1 file changed, 151 insertions(+), 6 deletions(-)

diff --git a/drivers/iommu/arm-smmu-v3.c b/drivers/iommu/arm-smmu-v3.c
index 665365b5f02e..ae10c78d20a8 100644
--- a/drivers/iommu/arm-smmu-v3.c
+++ b/drivers/iommu/arm-smmu-v3.c
@@ -539,6 +539,7 @@ struct arm_smmu_s1_cfg {
        struct iommu_pasid_table_cfg    tables;
        struct iommu_pasid_table_ops    *ops;
        struct iommu_pasid_entry        *cd0; /* Default context */
+       int                             pasid;
 };
 
 struct arm_smmu_s2_cfg {
@@ -656,6 +657,7 @@ struct arm_smmu_master_data {
        struct device                   *dev;
        size_t                          ssid_bits;
        bool                            can_fault;
+       bool                            auxd;
 };
 
 /* SMMU private data for an IOMMU domain */
@@ -715,6 +717,13 @@ static struct arm_smmu_mm *to_smmu_mm(struct io_mm *io_mm)
        return container_of(io_mm, struct arm_smmu_mm, io_mm);
 }
 
+static struct arm_smmu_master_data *dev_to_master(struct device *dev)
+{
+       struct iommu_fwspec *fwspec = dev->iommu_fwspec;
+
+       return fwspec ? fwspec->iommu_priv : NULL;
+}
+
 static void parse_driver_options(struct arm_smmu_device *smmu)
 {
        int i = 0;
@@ -2007,10 +2016,12 @@ static void arm_smmu_domain_free(struct iommu_domain 
*domain)
        if (smmu_domain->stage == ARM_SMMU_DOMAIN_S1) {
                struct iommu_pasid_table_ops *ops = smmu_domain->s1_cfg.ops;
 
-               if (ops) {
+               if (smmu_domain->s1_cfg.pasid)
+                       iommu_sva_free_pasid(smmu_domain->s1_cfg.pasid);
+               if (smmu_domain->s1_cfg.cd0)
                        iommu_free_pasid_entry(smmu_domain->s1_cfg.cd0);
+               if (ops)
                        iommu_free_pasid_ops(ops);
-               }
        } else {
                struct arm_smmu_s2_cfg *cfg = &smmu_domain->s2_cfg;
                if (cfg->vmid)
@@ -2068,6 +2079,28 @@ static int arm_smmu_domain_finalise_s1(struct 
arm_smmu_domain *smmu_domain,
        return ret;
 }
 
+static int arm_smmu_domain_finalise_aux(struct arm_smmu_domain *smmu_domain,
+                                       struct arm_smmu_master_data *master,
+                                       struct io_pgtable_cfg *pgtbl_cfg)
+{
+       struct iommu_pasid_entry *entry;
+       struct iommu_pasid_table_ops *ops;
+
+       if (!master->ste.s1_cfg)
+               return -EINVAL;
+
+       ops = master->ste.s1_cfg->ops;
+       if (!ops)
+               return -EINVAL;
+
+       entry = ops->alloc_priv_entry(ops, ARM_64_LPAE_S1, pgtbl_cfg);
+       if (IS_ERR(entry))
+               return PTR_ERR(entry);
+
+       smmu_domain->s1_cfg.cd0 = entry; /* FIXME: cd, not cd0 */
+       return 0;
+}
+
 static int arm_smmu_domain_finalise_s2(struct arm_smmu_domain *smmu_domain,
                                       struct arm_smmu_master_data *master,
                                       struct io_pgtable_cfg *pgtbl_cfg)
@@ -2130,6 +2163,13 @@ static int arm_smmu_domain_finalise(struct iommu_domain 
*domain,
                return -EINVAL;
        }
 
+       if (master->auxd) {
+               if (WARN_ON(smmu_domain->stage != ARM_SMMU_DOMAIN_S1))
+                       return -EINVAL;
+               /* We're initializing an auxiliary domain */
+               finalise_stage_fn = arm_smmu_domain_finalise_aux;
+       }
+
        pgtbl_cfg = (struct io_pgtable_cfg) {
                .pgsize_bitmap  = smmu->pgsize_bitmap,
                .ias            = ias,
@@ -2202,11 +2242,36 @@ static void arm_smmu_install_ste_for_dev(struct 
iommu_fwspec *fwspec)
        }
 }
 
+static void arm_smmu_detach_dev_aux(struct arm_smmu_domain *smmu_domain,
+                                   struct arm_smmu_master_data *master)
+{
+       int pasid;
+       struct iommu_pasid_table_ops *ops = master->ste.s1_cfg->ops;
+
+       if (WARN_ON(!ops))
+               return;
+
+       pasid = smmu_domain->s1_cfg.pasid;
+       if (WARN_ON(!pasid))
+               return;
+       ops->clear_entry(ops, pasid, smmu_domain->s1_cfg.cd0);
+       arm_smmu_atc_inv_master_all(master, pasid);
+       /*
+        * We don't count the number of attached devices, so free the
+        * PASID in domain_free.
+        */
+}
+
 static void arm_smmu_detach_dev(struct iommu_domain *domain, struct device 
*dev)
 {
        unsigned long flags;
        struct arm_smmu_master_data *master = dev->iommu_fwspec->iommu_priv;
-       struct arm_smmu_domain *smmu_domain = master->domain;
+       struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain);
+
+       if (master->auxd) {
+               arm_smmu_detach_dev_aux(smmu_domain, master);
+               return;
+       }
 
        if (smmu_domain) {
                iommu_sva_unbind_device_all(dev);
@@ -2224,6 +2289,39 @@ static void arm_smmu_detach_dev(struct iommu_domain 
*domain, struct device *dev)
        arm_smmu_install_ste_for_dev(dev->iommu_fwspec);
 }
 
+static int arm_smmu_attach_dev_aux(struct arm_smmu_domain *smmu_domain,
+                                  struct arm_smmu_master_data *master)
+{
+       int pasid, ret;
+       struct iommu_pasid_table_ops *ops = master->ste.s1_cfg->ops;
+
+       if (!ops)
+               return -EINVAL;
+
+       if (smmu_domain->s1_cfg.pasid) {
+               /*
+                * Reuse PASID if already allocated for another device. As the
+                * PASID space is global and this is the same SMMU, we know that
+                * the below set_entry will work.
+                *
+                * TODO: check device boundaries (sva_param)
+                */
+               pasid = smmu_domain->s1_cfg.pasid;
+       } else {
+               pasid = iommu_sva_alloc_pasid(master->dev);
+               if (pasid < 0)
+                       return pasid;
+
+               smmu_domain->s1_cfg.pasid = pasid;
+       }
+
+       ret = ops->set_entry(ops, pasid, smmu_domain->s1_cfg.cd0);
+       if (ret)
+               return ret;
+
+       return 1; /* Auxd needs special value. */
+}
+
 static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev)
 {
        int ret = 0;
@@ -2240,8 +2338,12 @@ static int arm_smmu_attach_dev(struct iommu_domain 
*domain, struct device *dev)
        smmu = master->smmu;
        ste = &master->ste;
 
+       /* Spurious attach_dev when doing detach on an aux domain... */
+       if (master->domain == smmu_domain)
+               return !!master->auxd;
+
        /* Already attached to a different domain? */
-       if (master->domain)
+       if (master->domain && !master->auxd)
                arm_smmu_detach_dev(&master->domain->domain, dev);
 
        mutex_lock(&smmu_domain->init_mutex);
@@ -2262,6 +2364,12 @@ static int arm_smmu_attach_dev(struct iommu_domain 
*domain, struct device *dev)
                goto out_unlock;
        }
 
+       if (master->auxd) {
+               /* Now install the context descriptor */
+               ret = arm_smmu_attach_dev_aux(smmu_domain, master);
+               goto out_unlock;
+       }
+
        ste->assigned = true;
        master->domain = smmu_domain;
 
@@ -2411,7 +2519,7 @@ static int arm_smmu_sva_init(struct device *dev, struct 
iommu_sva_param *param)
        if (!master->ssid_bits)
                return -EINVAL;
 
-       if (param->features & ~IOMMU_SVA_FEAT_IOPF)
+       if (param->features & ~(IOMMU_SVA_FEAT_IOPF | IOMMU_SVA_FEAT_AUXD))
                return -EINVAL;
 
        if (param->features & IOMMU_SVA_FEAT_IOPF) {
@@ -2426,6 +2534,9 @@ static int arm_smmu_sva_init(struct device *dev, struct 
iommu_sva_param *param)
                        goto err_disable_pri;
        }
 
+       if (param->features & IOMMU_SVA_FEAT_AUXD)
+               master->auxd = true;
+
        if (!param->max_pasid)
                param->max_pasid = 0xfffffU;
 
@@ -2443,7 +2554,13 @@ static int arm_smmu_sva_init(struct device *dev, struct 
iommu_sva_param *param)
 
 static void arm_smmu_sva_shutdown(struct device *dev)
 {
-       arm_smmu_disable_pri(dev->iommu_fwspec->iommu_priv);
+       struct arm_smmu_master_data *master = dev_to_master(dev);
+
+       if (!master)
+               return;
+
+       master->auxd = false;
+       arm_smmu_disable_pri(master);
        iopf_queue_remove_device(dev);
 }
 
@@ -2874,6 +2991,11 @@ static int arm_smmu_domain_get_attr(struct iommu_domain 
*domain,
        case DOMAIN_ATTR_NESTING:
                *(int *)data = (smmu_domain->stage == ARM_SMMU_DOMAIN_NESTED);
                return 0;
+       case DOMAIN_ATTR_AUXD_ID:
+               if (smmu_domain->stage != ARM_SMMU_DOMAIN_S1)
+                       return -EINVAL;
+               *(int *)data = smmu_domain->s1_cfg.pasid;
+               return 0;
        default:
                return -ENODEV;
        }
@@ -2912,6 +3034,28 @@ static int arm_smmu_domain_set_attr(struct iommu_domain 
*domain,
        return ret;
 }
 
+static int arm_smmu_get_dev_attr(struct device *dev, enum iommu_dev_attr attr,
+                                void *data)
+{
+       bool *auxd;
+       struct arm_smmu_master_data *master = dev_to_master(dev);
+
+       if (!master || !data)
+               return -ENODEV;
+
+       switch (attr) {
+       case IOMMU_DEV_ATTR_AUXD_CAPABILITY:
+               auxd = data;
+               /* PASID supported */
+               *auxd = !!master->ssid_bits;
+               break;
+       default:
+               return -ENODEV;
+       }
+
+       return 0;
+}
+
 static int arm_smmu_of_xlate(struct device *dev, struct of_phandle_args *args)
 {
        return iommu_fwspec_add_ids(dev, args->args, 1);
@@ -2966,6 +3110,7 @@ static struct iommu_ops arm_smmu_ops = {
        .device_group           = arm_smmu_device_group,
        .domain_get_attr        = arm_smmu_domain_get_attr,
        .domain_set_attr        = arm_smmu_domain_set_attr,
+       .get_dev_attr           = arm_smmu_get_dev_attr,
        .of_xlate               = arm_smmu_of_xlate,
        .get_resv_regions       = arm_smmu_get_resv_regions,
        .put_resv_regions       = arm_smmu_put_resv_regions,
-- 
2.19.1

_______________________________________________
iommu mailing list
iommu@lists.linux-foundation.org
https://lists.linuxfoundation.org/mailman/listinfo/iommu

Reply via email to