]> asedeno.scripts.mit.edu Git - linux.git/blobdiff - drivers/iommu/of_iommu.c
iommu: of: Handle IOMMU lookup failure with deferred probing or error
[linux.git] / drivers / iommu / of_iommu.c
index 2683e9fc0dcf5cc0886034b7fccafb8861d958f1..9f44ee8ea1bc8a10a6ff4c0dad297b78b8c2f57a 100644 (file)
@@ -96,6 +96,49 @@ int of_get_dma_window(struct device_node *dn, const char *prefix, int index,
 }
 EXPORT_SYMBOL_GPL(of_get_dma_window);
 
+static bool of_iommu_driver_present(struct device_node *np)
+{
+       /*
+        * If the IOMMU still isn't ready by the time we reach init, assume
+        * it never will be. We don't want to defer indefinitely, nor attempt
+        * to dereference __iommu_of_table after it's been freed.
+        */
+       if (system_state > SYSTEM_BOOTING)
+               return false;
+
+       return of_match_node(&__iommu_of_table, np);
+}
+
+static const struct iommu_ops
+*of_iommu_xlate(struct device *dev, struct of_phandle_args *iommu_spec)
+{
+       const struct iommu_ops *ops;
+       struct fwnode_handle *fwnode = &iommu_spec->np->fwnode;
+       int err;
+
+       ops = iommu_ops_from_fwnode(fwnode);
+       if ((ops && !ops->of_xlate) ||
+           (!ops && !of_iommu_driver_present(iommu_spec->np)))
+               return NULL;
+
+       err = iommu_fwspec_init(dev, &iommu_spec->np->fwnode, ops);
+       if (err)
+               return ERR_PTR(err);
+       /*
+        * The otherwise-empty fwspec handily serves to indicate the specific
+        * IOMMU device we're waiting for, which will be useful if we ever get
+        * a proper probe-ordering dependency mechanism in future.
+        */
+       if (!ops)
+               return ERR_PTR(-EPROBE_DEFER);
+
+       err = ops->of_xlate(dev, iommu_spec);
+       if (err)
+               return ERR_PTR(err);
+
+       return ops;
+}
+
 static int __get_pci_rid(struct pci_dev *pdev, u16 alias, void *data)
 {
        struct of_phandle_args *iommu_spec = data;
@@ -105,10 +148,11 @@ static int __get_pci_rid(struct pci_dev *pdev, u16 alias, void *data)
 }
 
 static const struct iommu_ops
-*of_pci_iommu_configure(struct pci_dev *pdev, struct device_node *bridge_np)
+*of_pci_iommu_init(struct pci_dev *pdev, struct device_node *bridge_np)
 {
        const struct iommu_ops *ops;
        struct of_phandle_args iommu_spec;
+       int err;
 
        /*
         * Start by tracing the RID alias down the PCI topology as
@@ -123,56 +167,76 @@ static const struct iommu_ops
         * bus into the system beyond, and which IOMMU it ends up at.
         */
        iommu_spec.np = NULL;
-       if (of_pci_map_rid(bridge_np, iommu_spec.args[0], "iommu-map",
-                          "iommu-map-mask", &iommu_spec.np, iommu_spec.args))
-               return NULL;
+       err = of_pci_map_rid(bridge_np, iommu_spec.args[0], "iommu-map",
+                            "iommu-map-mask", &iommu_spec.np,
+                            iommu_spec.args);
+       if (err)
+               return err == -ENODEV ? NULL : ERR_PTR(err);
 
-       ops = iommu_ops_from_fwnode(&iommu_spec.np->fwnode);
-       if (!ops || !ops->of_xlate ||
-           iommu_fwspec_init(&pdev->dev, &iommu_spec.np->fwnode, ops) ||
-           ops->of_xlate(&pdev->dev, &iommu_spec))
-               ops = NULL;
+       ops = of_iommu_xlate(&pdev->dev, &iommu_spec);
 
        of_node_put(iommu_spec.np);
        return ops;
 }
 
-const struct iommu_ops *of_iommu_configure(struct device *dev,
-                                          struct device_node *master_np)
+static const struct iommu_ops
+*of_platform_iommu_init(struct device *dev, struct device_node *np)
 {
        struct of_phandle_args iommu_spec;
-       struct device_node *np;
        const struct iommu_ops *ops = NULL;
        int idx = 0;
 
-       if (dev_is_pci(dev))
-               return of_pci_iommu_configure(to_pci_dev(dev), master_np);
-
        /*
         * We don't currently walk up the tree looking for a parent IOMMU.
         * See the `Notes:' section of
         * Documentation/devicetree/bindings/iommu/iommu.txt
         */
-       while (!of_parse_phandle_with_args(master_np, "iommus",
-                                          "#iommu-cells", idx,
-                                          &iommu_spec)) {
-               np = iommu_spec.np;
-               ops = iommu_ops_from_fwnode(&np->fwnode);
-
-               if (!ops || !ops->of_xlate ||
-                   iommu_fwspec_init(dev, &np->fwnode, ops) ||
-                   ops->of_xlate(dev, &iommu_spec))
-                       goto err_put_node;
-
-               of_node_put(np);
+       while (!of_parse_phandle_with_args(np, "iommus", "#iommu-cells",
+                                          idx, &iommu_spec)) {
+               ops = of_iommu_xlate(dev, &iommu_spec);
+               of_node_put(iommu_spec.np);
                idx++;
+               if (IS_ERR_OR_NULL(ops))
+                       break;
        }
 
        return ops;
+}
+
+const struct iommu_ops *of_iommu_configure(struct device *dev,
+                                          struct device_node *master_np)
+{
+       const struct iommu_ops *ops;
+       struct iommu_fwspec *fwspec = dev->iommu_fwspec;
+
+       if (!master_np)
+               return NULL;
+
+       if (fwspec) {
+               if (fwspec->ops)
+                       return fwspec->ops;
+
+               /* In the deferred case, start again from scratch */
+               iommu_fwspec_free(dev);
+       }
 
-err_put_node:
-       of_node_put(np);
-       return NULL;
+       if (dev_is_pci(dev))
+               ops = of_pci_iommu_init(to_pci_dev(dev), master_np);
+       else
+               ops = of_platform_iommu_init(dev, master_np);
+       /*
+        * If we have reason to believe the IOMMU driver missed the initial
+        * add_device callback for dev, replay it to get things in order.
+        */
+       if (!IS_ERR_OR_NULL(ops) && ops->add_device &&
+           dev->bus && !dev->iommu_group) {
+               int err = ops->add_device(dev);
+
+               if (err)
+                       ops = ERR_PTR(err);
+       }
+
+       return ops;
 }
 
 static int __init of_iommu_init(void)
@@ -183,7 +247,7 @@ static int __init of_iommu_init(void)
        for_each_matching_node_and_match(np, matches, &match) {
                const of_iommu_init_fn init_fn = match->data;
 
-               if (init_fn(np))
+               if (init_fn && init_fn(np))
                        pr_err("Failed to initialise IOMMU %s\n",
                                of_node_full_name(np));
        }