bus: platform,amba,fsl-mc,PCI: Add device DMA ownership management
authorLu Baolu <baolu.lu@linux.intel.com>
Mon, 18 Apr 2022 00:49:53 +0000 (08:49 +0800)
committerJoerg Roedel <jroedel@suse.de>
Thu, 28 Apr 2022 13:32:20 +0000 (15:32 +0200)
The devices on platform/amba/fsl-mc/PCI buses could be bound to drivers
with the device DMA managed by kernel drivers or user-space applications.
Unfortunately, multiple devices may be placed in the same IOMMU group
because they cannot be isolated from each other. The DMA on these devices
must either be entirely under kernel control or userspace control, never
a mixture. Otherwise the driver integrity is not guaranteed because they
could access each other through the peer-to-peer accesses which by-pass
the IOMMU protection.

This checks and sets the default DMA mode during driver binding, and
cleanups during driver unbinding. In the default mode, the device DMA is
managed by the device driver which handles DMA operations through the
kernel DMA APIs (see Documentation/core-api/dma-api.rst).

For cases where the devices are assigned for userspace control through the
userspace driver framework(i.e. VFIO), the drivers(for example, vfio_pci/
vfio_platfrom etc.) may set a new flag (driver_managed_dma) to skip this
default setting in the assumption that the drivers know what they are
doing with the device DMA.

Calling iommu_device_use_default_domain() before {of,acpi}_dma_configure
is currently a problem. As things stand, the IOMMU driver ignored the
initial iommu_probe_device() call when the device was added, since at
that point it had no fwspec yet. In this situation,
{of,acpi}_iommu_configure() are retriggering iommu_probe_device() after
the IOMMU driver has seen the firmware data via .of_xlate to learn that
it actually responsible for the given device. As the result, before
that gets fixed, iommu_use_default_domain() goes at the end, and calls
arch_teardown_dma_ops() if it fails.

Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Bjorn Helgaas <bhelgaas@google.com>
Cc: Stuart Yoder <stuyoder@gmail.com>
Cc: Laurentiu Tudor <laurentiu.tudor@nxp.com>
Signed-off-by: Lu Baolu <baolu.lu@linux.intel.com>
Reviewed-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Reviewed-by: Jason Gunthorpe <jgg@nvidia.com>
Reviewed-by: Robin Murphy <robin.murphy@arm.com>
Tested-by: Eric Auger <eric.auger@redhat.com>
Link: https://lore.kernel.org/r/20220418005000.897664-5-baolu.lu@linux.intel.com
Signed-off-by: Joerg Roedel <jroedel@suse.de>
drivers/amba/bus.c
drivers/base/platform.c
drivers/bus/fsl-mc/fsl-mc-bus.c
drivers/pci/pci-driver.c
include/linux/amba/bus.h
include/linux/fsl/mc.h
include/linux/pci.h
include/linux/platform_device.h

index 76b52bd2c2a44beac9082c11bc406b7995cd46fc..a0ec61232b6c4613605b9b811eb94379be5e84d1 100644 (file)
@@ -22,6 +22,8 @@
 #include <linux/of_irq.h>
 #include <linux/of_device.h>
 #include <linux/acpi.h>
+#include <linux/iommu.h>
+#include <linux/dma-map-ops.h>
 
 #define to_amba_driver(d)      container_of(d, struct amba_driver, drv)
 
@@ -277,6 +279,7 @@ static void amba_shutdown(struct device *dev)
 
 static int amba_dma_configure(struct device *dev)
 {
+       struct amba_driver *drv = to_amba_driver(dev->driver);
        enum dev_dma_attr attr;
        int ret = 0;
 
@@ -287,9 +290,23 @@ static int amba_dma_configure(struct device *dev)
                ret = acpi_dma_configure(dev, attr);
        }
 
+       if (!ret && !drv->driver_managed_dma) {
+               ret = iommu_device_use_default_domain(dev);
+               if (ret)
+                       arch_teardown_dma_ops(dev);
+       }
+
        return ret;
 }
 
+static void amba_dma_cleanup(struct device *dev)
+{
+       struct amba_driver *drv = to_amba_driver(dev->driver);
+
+       if (!drv->driver_managed_dma)
+               iommu_device_unuse_default_domain(dev);
+}
+
 #ifdef CONFIG_PM
 /*
  * Hooks to provide runtime PM of the pclk (bus clock).  It is safe to
@@ -359,6 +376,7 @@ struct bus_type amba_bustype = {
        .remove         = amba_remove,
        .shutdown       = amba_shutdown,
        .dma_configure  = amba_dma_configure,
+       .dma_cleanup    = amba_dma_cleanup,
        .pm             = &amba_pm,
 };
 EXPORT_SYMBOL_GPL(amba_bustype);
index d7915734d931d720642c40bc9005910d4f2afc16..70bc30cf575c5131d85d80ebc466148cdc49910f 100644 (file)
@@ -30,6 +30,8 @@
 #include <linux/property.h>
 #include <linux/kmemleak.h>
 #include <linux/types.h>
+#include <linux/iommu.h>
+#include <linux/dma-map-ops.h>
 
 #include "base.h"
 #include "power/power.h"
@@ -1456,6 +1458,7 @@ static void platform_shutdown(struct device *_dev)
 
 static int platform_dma_configure(struct device *dev)
 {
+       struct platform_driver *drv = to_platform_driver(dev->driver);
        enum dev_dma_attr attr;
        int ret = 0;
 
@@ -1466,9 +1469,23 @@ static int platform_dma_configure(struct device *dev)
                ret = acpi_dma_configure(dev, attr);
        }
 
+       if (!ret && !drv->driver_managed_dma) {
+               ret = iommu_device_use_default_domain(dev);
+               if (ret)
+                       arch_teardown_dma_ops(dev);
+       }
+
        return ret;
 }
 
+static void platform_dma_cleanup(struct device *dev)
+{
+       struct platform_driver *drv = to_platform_driver(dev->driver);
+
+       if (!drv->driver_managed_dma)
+               iommu_device_unuse_default_domain(dev);
+}
+
 static const struct dev_pm_ops platform_dev_pm_ops = {
        SET_RUNTIME_PM_OPS(pm_generic_runtime_suspend, pm_generic_runtime_resume, NULL)
        USE_PLATFORM_PM_SLEEP_OPS
@@ -1483,6 +1500,7 @@ struct bus_type platform_bus_type = {
        .remove         = platform_remove,
        .shutdown       = platform_shutdown,
        .dma_configure  = platform_dma_configure,
+       .dma_cleanup    = platform_dma_cleanup,
        .pm             = &platform_dev_pm_ops,
 };
 EXPORT_SYMBOL_GPL(platform_bus_type);
index 8fd4a356a86ec0b1cff2b830c6c1bf78924682f0..76648c4fdaf4be187d7b0e78e8f2d9a6a5db1ede 100644 (file)
@@ -21,6 +21,7 @@
 #include <linux/dma-mapping.h>
 #include <linux/acpi.h>
 #include <linux/iommu.h>
+#include <linux/dma-map-ops.h>
 
 #include "fsl-mc-private.h"
 
@@ -140,15 +141,33 @@ static int fsl_mc_dma_configure(struct device *dev)
 {
        struct device *dma_dev = dev;
        struct fsl_mc_device *mc_dev = to_fsl_mc_device(dev);
+       struct fsl_mc_driver *mc_drv = to_fsl_mc_driver(dev->driver);
        u32 input_id = mc_dev->icid;
+       int ret;
 
        while (dev_is_fsl_mc(dma_dev))
                dma_dev = dma_dev->parent;
 
        if (dev_of_node(dma_dev))
-               return of_dma_configure_id(dev, dma_dev->of_node, 0, &input_id);
+               ret = of_dma_configure_id(dev, dma_dev->of_node, 0, &input_id);
+       else
+               ret = acpi_dma_configure_id(dev, DEV_DMA_COHERENT, &input_id);
+
+       if (!ret && !mc_drv->driver_managed_dma) {
+               ret = iommu_device_use_default_domain(dev);
+               if (ret)
+                       arch_teardown_dma_ops(dev);
+       }
+
+       return ret;
+}
+
+static void fsl_mc_dma_cleanup(struct device *dev)
+{
+       struct fsl_mc_driver *mc_drv = to_fsl_mc_driver(dev->driver);
 
-       return acpi_dma_configure_id(dev, DEV_DMA_COHERENT, &input_id);
+       if (!mc_drv->driver_managed_dma)
+               iommu_device_unuse_default_domain(dev);
 }
 
 static ssize_t modalias_show(struct device *dev, struct device_attribute *attr,
@@ -312,6 +331,7 @@ struct bus_type fsl_mc_bus_type = {
        .match = fsl_mc_bus_match,
        .uevent = fsl_mc_bus_uevent,
        .dma_configure  = fsl_mc_dma_configure,
+       .dma_cleanup = fsl_mc_dma_cleanup,
        .dev_groups = fsl_mc_dev_groups,
        .bus_groups = fsl_mc_bus_groups,
 };
index 4ceeb75fc8996f03752f2cbf39900c2f862bf3c8..f83f7fbac68f8a2d5a4fedaced120854be25acff 100644 (file)
@@ -20,6 +20,7 @@
 #include <linux/of_device.h>
 #include <linux/acpi.h>
 #include <linux/dma-map-ops.h>
+#include <linux/iommu.h>
 #include "pci.h"
 #include "pcie/portdrv.h"
 
@@ -1601,6 +1602,7 @@ static int pci_bus_num_vf(struct device *dev)
  */
 static int pci_dma_configure(struct device *dev)
 {
+       struct pci_driver *driver = to_pci_driver(dev->driver);
        struct device *bridge;
        int ret = 0;
 
@@ -1616,9 +1618,24 @@ static int pci_dma_configure(struct device *dev)
        }
 
        pci_put_host_bridge_device(bridge);
+
+       if (!ret && !driver->driver_managed_dma) {
+               ret = iommu_device_use_default_domain(dev);
+               if (ret)
+                       arch_teardown_dma_ops(dev);
+       }
+
        return ret;
 }
 
+static void pci_dma_cleanup(struct device *dev)
+{
+       struct pci_driver *driver = to_pci_driver(dev->driver);
+
+       if (!driver->driver_managed_dma)
+               iommu_device_unuse_default_domain(dev);
+}
+
 struct bus_type pci_bus_type = {
        .name           = "pci",
        .match          = pci_bus_match,
@@ -1632,6 +1649,7 @@ struct bus_type pci_bus_type = {
        .pm             = PCI_PM_OPS_PTR,
        .num_vf         = pci_bus_num_vf,
        .dma_configure  = pci_dma_configure,
+       .dma_cleanup    = pci_dma_cleanup,
 };
 EXPORT_SYMBOL(pci_bus_type);
 
index 6562f543c3e04d07ef93613439e1736e263eb598..2ddce9bcd00ed2dd23e6088e0b665a2f2ba8eb21 100644 (file)
@@ -79,6 +79,14 @@ struct amba_driver {
        void                    (*remove)(struct amba_device *);
        void                    (*shutdown)(struct amba_device *);
        const struct amba_id    *id_table;
+       /*
+        * For most device drivers, no need to care about this flag as long as
+        * all DMAs are handled through the kernel DMA API. For some special
+        * ones, for example VFIO drivers, they know how to manage the DMA
+        * themselves and set this flag so that the IOMMU layer will allow them
+        * to setup and manage their own I/O address space.
+        */
+       bool driver_managed_dma;
 };
 
 /*
index 7b6c42bfb660fc200a37586577428917d1fda070..27efef8affb1a4756052f6bef1cde0987840bcd0 100644 (file)
@@ -32,6 +32,13 @@ struct fsl_mc_io;
  * @shutdown: Function called at shutdown time to quiesce the device
  * @suspend: Function called when a device is stopped
  * @resume: Function called when a device is resumed
+ * @driver_managed_dma: Device driver doesn't use kernel DMA API for DMA.
+ *             For most device drivers, no need to care about this flag
+ *             as long as all DMAs are handled through the kernel DMA API.
+ *             For some special ones, for example VFIO drivers, they know
+ *             how to manage the DMA themselves and set this flag so that
+ *             the IOMMU layer will allow them to setup and manage their
+ *             own I/O address space.
  *
  * Generic DPAA device driver object for device drivers that are registered
  * with a DPRC bus. This structure is to be embedded in each device-specific
@@ -45,6 +52,7 @@ struct fsl_mc_driver {
        void (*shutdown)(struct fsl_mc_device *dev);
        int (*suspend)(struct fsl_mc_device *dev, pm_message_t state);
        int (*resume)(struct fsl_mc_device *dev);
+       bool driver_managed_dma;
 };
 
 #define to_fsl_mc_driver(_drv) \
index 60adf42460abbb340e4e0810ac4a68a953274f9c..b933d2b08d4dd50bb09fe2e272d581eebbc71a98 100644 (file)
@@ -895,6 +895,13 @@ struct module;
  *              created once it is bound to the driver.
  * @driver:    Driver model structure.
  * @dynids:    List of dynamically added device IDs.
+ * @driver_managed_dma: Device driver doesn't use kernel DMA API for DMA.
+ *             For most device drivers, no need to care about this flag
+ *             as long as all DMAs are handled through the kernel DMA API.
+ *             For some special ones, for example VFIO drivers, they know
+ *             how to manage the DMA themselves and set this flag so that
+ *             the IOMMU layer will allow them to setup and manage their
+ *             own I/O address space.
  */
 struct pci_driver {
        struct list_head        node;
@@ -913,6 +920,7 @@ struct pci_driver {
        const struct attribute_group **dev_groups;
        struct device_driver    driver;
        struct pci_dynids       dynids;
+       bool driver_managed_dma;
 };
 
 static inline struct pci_driver *to_pci_driver(struct device_driver *drv)
index 17fde717df68c9183c00a91383e18dba68894b36..b3d9c744f1e599517abf1b631ee53dd1e195fb17 100644 (file)
@@ -210,6 +210,14 @@ struct platform_driver {
        struct device_driver driver;
        const struct platform_device_id *id_table;
        bool prevent_deferred_probe;
+       /*
+        * For most device drivers, no need to care about this flag as long as
+        * all DMAs are handled through the kernel DMA API. For some special
+        * ones, for example VFIO drivers, they know how to manage the DMA
+        * themselves and set this flag so that the IOMMU layer will allow them
+        * to setup and manage their own I/O address space.
+        */
+       bool driver_managed_dma;
 };
 
 #define to_platform_driver(drv)        (container_of((drv), struct platform_driver, \