Skip to content

Commit

Permalink
bus: platform,amba,fsl-mc,PCI: Add device DMA ownership management
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Lu Baolu authored and Joerg Roedel committed Apr 28, 2022
1 parent 4a6d9dd commit 512881e
Show file tree
Hide file tree
Showing 8 changed files with 108 additions and 2 deletions.
18 changes: 18 additions & 0 deletions drivers/amba/bus.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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;

Expand All @@ -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
Expand Down Expand Up @@ -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);
Expand Down
18 changes: 18 additions & 0 deletions drivers/base/platform.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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;

Expand All @@ -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
Expand All @@ -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);
Expand Down
24 changes: 22 additions & 2 deletions drivers/bus/fsl-mc/fsl-mc-bus.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
};
Expand Down
18 changes: 18 additions & 0 deletions drivers/pci/pci-driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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;

Expand All @@ -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,
Expand All @@ -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);

Expand Down
8 changes: 8 additions & 0 deletions include/linux/amba/bus.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};

/*
Expand Down
8 changes: 8 additions & 0 deletions include/linux/fsl/mc.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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) \
Expand Down
8 changes: 8 additions & 0 deletions include/linux/pci.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)
Expand Down
8 changes: 8 additions & 0 deletions include/linux/platform_device.h
Original file line number Diff line number Diff line change
Expand Up @@ -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, \
Expand Down

0 comments on commit 512881e

Please sign in to comment.