Skip to content

Commit

Permalink
drm/tegra: Optionally attach clients to the IOMMU
Browse files Browse the repository at this point in the history
If a client is already attached to an IOMMU domain that is not the
shared domain, don't try to attach it again. This allows using the
IOMMU-backed DMA API.

Since the IOMMU-backed DMA API is now supported and there's no way
to detach from it on 64-bit ARM, don't bother to detach from it on
32-bit ARM either.

Signed-off-by: Thierry Reding <treding@nvidia.com>
  • Loading branch information
Thierry Reding committed Oct 29, 2019
1 parent 2e8d874 commit fa6661b
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 18 deletions.
66 changes: 48 additions & 18 deletions drivers/gpu/drm/tegra/drm.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,6 @@
#include <drm/drm_prime.h>
#include <drm/drm_vblank.h>

#if IS_ENABLED(CONFIG_ARM_DMA_USE_IOMMU)
#include <asm/dma-iommu.h>
#endif

#include "drm.h"
#include "gem.h"

Expand Down Expand Up @@ -908,37 +904,36 @@ int tegra_drm_unregister_client(struct tegra_drm *tegra,

int host1x_client_iommu_attach(struct host1x_client *client)
{
struct iommu_domain *domain = iommu_get_domain_for_dev(client->dev);
struct drm_device *drm = dev_get_drvdata(client->parent);
struct tegra_drm *tegra = drm->dev_private;
struct iommu_group *group = NULL;
int err;

if (tegra->domain) {
struct iommu_domain *domain;
/*
* If the host1x client is already attached to an IOMMU domain that is
* not the shared IOMMU domain, don't try to attach it to a different
* domain. This allows using the IOMMU-backed DMA API.
*/
if (domain && domain != tegra->domain)
return 0;

if (tegra->domain) {
group = iommu_group_get(client->dev);
if (!group) {
dev_err(client->dev, "failed to get IOMMU group\n");
return -ENODEV;
}

#if IS_ENABLED(CONFIG_ARM_DMA_USE_IOMMU)
if (client->dev->archdata.mapping) {
struct dma_iommu_mapping *mapping =
to_dma_iommu_mapping(client->dev);
arm_iommu_detach_device(client->dev);
arm_iommu_release_mapping(mapping);
}
#endif

domain = iommu_get_domain_for_dev(client->dev);
if (domain != tegra->domain) {
err = iommu_attach_group(tegra->domain, group);
if (err < 0) {
iommu_group_put(group);
return err;
}
}

tegra->use_explicit_iommu = true;
}

client->group = group;
Expand All @@ -963,6 +958,7 @@ void host1x_client_iommu_detach(struct host1x_client *client)
iommu_detach_group(tegra->domain, client->group);

iommu_group_put(client->group);
client->group = NULL;
}
}

Expand Down Expand Up @@ -1046,6 +1042,7 @@ void tegra_drm_free(struct tegra_drm *tegra, size_t size, void *virt,
static int host1x_drm_probe(struct host1x_device *dev)
{
struct drm_driver *driver = &tegra_drm_driver;
struct iommu_domain *domain;
struct tegra_drm *tegra;
struct drm_device *drm;
int err;
Expand All @@ -1060,7 +1057,36 @@ static int host1x_drm_probe(struct host1x_device *dev)
goto put;
}

if (iommu_present(&platform_bus_type)) {
/*
* If the Tegra DRM clients are backed by an IOMMU, push buffers are
* likely to be allocated beyond the 32-bit boundary if sufficient
* system memory is available. This is problematic on earlier Tegra
* generations where host1x supports a maximum of 32 address bits in
* the GATHER opcode. In this case, unless host1x is behind an IOMMU
* as well it won't be able to process buffers allocated beyond the
* 32-bit boundary.
*
* The DMA API will use bounce buffers in this case, so that could
* perhaps still be made to work, even if less efficient, but there
* is another catch: in order to perform cache maintenance on pages
* allocated for discontiguous buffers we need to map and unmap the
* SG table representing these buffers. This is fine for something
* small like a push buffer, but it exhausts the bounce buffer pool
* (typically on the order of a few MiB) for framebuffers (many MiB
* for any modern resolution).
*
* Work around this by making sure that Tegra DRM clients only use
* an IOMMU if the parent host1x also uses an IOMMU.
*
* Note that there's still a small gap here that we don't cover: if
* the DMA API is backed by an IOMMU there's no way to control which
* device is attached to an IOMMU and which isn't, except via wiring
* up the device tree appropriately. This is considered an problem
* of integration, so care must be taken for the DT to be consistent.
*/
domain = iommu_get_domain_for_dev(drm->dev->parent);

if (domain && iommu_present(&platform_bus_type)) {
tegra->domain = iommu_domain_alloc(&platform_bus_type);
if (!tegra->domain) {
err = -ENOMEM;
Expand Down Expand Up @@ -1104,7 +1130,7 @@ static int host1x_drm_probe(struct host1x_device *dev)
if (err < 0)
goto fbdev;

if (tegra->domain) {
if (tegra->use_explicit_iommu) {
u64 carveout_start, carveout_end, gem_start, gem_end;
u64 dma_mask = dma_get_mask(&dev->dev);
dma_addr_t start, end;
Expand Down Expand Up @@ -1132,6 +1158,10 @@ static int host1x_drm_probe(struct host1x_device *dev)
DRM_DEBUG_DRIVER(" GEM: %#llx-%#llx\n", gem_start, gem_end);
DRM_DEBUG_DRIVER(" Carveout: %#llx-%#llx\n", carveout_start,
carveout_end);
} else if (tegra->domain) {
iommu_domain_free(tegra->domain);
tegra->domain = NULL;
iova_cache_put();
}

if (tegra->hub) {
Expand Down
1 change: 1 addition & 0 deletions drivers/gpu/drm/tegra/drm.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ struct tegra_drm {
struct drm_device *drm;

struct iommu_domain *domain;
bool use_explicit_iommu;
struct mutex mm_lock;
struct drm_mm mm;

Expand Down

0 comments on commit fa6661b

Please sign in to comment.