From: Thierry Reding treding@nvidia.com
Hi,
this contains a couple of fixes for a DMA API performance regression that was introduced in v5.5 for older Tegra devices. Patches 1 and 2 will likely have to be backported to v5.5.
Thierry
Thierry Reding (3): drm/tegra: Relax IOMMU usage criteria on old Tegra drm/tegra: Reuse IOVA mapping where possible gpu: host1x: Set DMA direction only for DMA-mapped buffer objects
drivers/gpu/drm/tegra/drm.c | 49 +++++++++++++++++++++++------------ drivers/gpu/drm/tegra/gem.c | 10 ++++++- drivers/gpu/drm/tegra/plane.c | 44 +++++++++++++++++-------------- drivers/gpu/host1x/job.c | 34 +++++++++++++++++++++--- 4 files changed, 96 insertions(+), 41 deletions(-)
From: Thierry Reding treding@nvidia.com
Older Tegra devices only allow addressing 32 bits of memory, so whether or not the host1x is attached to an IOMMU doesn't matter. host1x IOMMU attachment is only needed on devices that can address memory beyond the 32-bit boundary and where the host1x doesn't support the wide GATHER opcode that allows it to access buffers at higher addresses.
Signed-off-by: Thierry Reding treding@nvidia.com --- drivers/gpu/drm/tegra/drm.c | 49 ++++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 17 deletions(-)
diff --git a/drivers/gpu/drm/tegra/drm.c b/drivers/gpu/drm/tegra/drm.c index aa9e49f04988..bd268028fb3d 100644 --- a/drivers/gpu/drm/tegra/drm.c +++ b/drivers/gpu/drm/tegra/drm.c @@ -1037,23 +1037,9 @@ void tegra_drm_free(struct tegra_drm *tegra, size_t size, void *virt, free_pages((unsigned long)virt, get_order(size)); }
-static int host1x_drm_probe(struct host1x_device *dev) +static bool host1x_drm_wants_iommu(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; - - drm = drm_dev_alloc(driver, &dev->dev); - if (IS_ERR(drm)) - return PTR_ERR(drm); - - tegra = kzalloc(sizeof(*tegra), GFP_KERNEL); - if (!tegra) { - err = -ENOMEM; - goto put; - }
/* * If the Tegra DRM clients are backed by an IOMMU, push buffers are @@ -1082,9 +1068,38 @@ static int host1x_drm_probe(struct host1x_device *dev) * 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); + domain = iommu_get_domain_for_dev(dev->dev.parent); + + /* + * Tegra20 and Tegra30 don't support addressing memory beyond the + * 32-bit boundary, so the regular GATHER opcodes will always be + * sufficient and whether or not the host1x is attached to an IOMMU + * doesn't matter. + */ + if (!domain && dma_get_mask(dev->dev.parent) <= DMA_BIT_MASK(32)) + return true; + + return domain != NULL; +} + +static int host1x_drm_probe(struct host1x_device *dev) +{ + struct drm_driver *driver = &tegra_drm_driver; + struct tegra_drm *tegra; + struct drm_device *drm; + int err; + + drm = drm_dev_alloc(driver, &dev->dev); + if (IS_ERR(drm)) + return PTR_ERR(drm); + + tegra = kzalloc(sizeof(*tegra), GFP_KERNEL); + if (!tegra) { + err = -ENOMEM; + goto put; + }
- if (domain && iommu_present(&platform_bus_type)) { + if (host1x_drm_wants_iommu(dev) && iommu_present(&platform_bus_type)) { tegra->domain = iommu_domain_alloc(&platform_bus_type); if (!tegra->domain) { err = -ENOMEM;
04.02.2020 16:59, Thierry Reding пишет:
From: Thierry Reding treding@nvidia.com
Older Tegra devices only allow addressing 32 bits of memory, so whether or not the host1x is attached to an IOMMU doesn't matter. host1x IOMMU attachment is only needed on devices that can address memory beyond the 32-bit boundary and where the host1x doesn't support the wide GATHER opcode that allows it to access buffers at higher addresses.
Signed-off-by: Thierry Reding treding@nvidia.com
drivers/gpu/drm/tegra/drm.c | 49 ++++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 17 deletions(-)
Tested-by: Dmitry Osipenko digetx@gmail.com
04.02.2020 16:59, Thierry Reding пишет:
From: Thierry Reding treding@nvidia.com
Older Tegra devices only allow addressing 32 bits of memory, so whether or not the host1x is attached to an IOMMU doesn't matter. host1x IOMMU attachment is only needed on devices that can address memory beyond the 32-bit boundary and where the host1x doesn't support the wide GATHER opcode that allows it to access buffers at higher addresses.
Signed-off-by: Thierry Reding treding@nvidia.com
This needs a stable tag:
Cc: stable@vger.kernel.org # v5.5
drivers/gpu/drm/tegra/drm.c | 49 ++++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 17 deletions(-)
Otherwise,
Reviewed-by: Dmitry Osipenko digetx@gmail.com
From: Thierry Reding treding@nvidia.com
This partially reverts the DMA API support that was recently merged because it was causing performance regressions on older Tegra devices. Unfortunately, the cache maintenance performed by dma_map_sg() and dma_unmap_sg() causes performance to drop by a factor of 10.
The right solution for this would be to cache mappings for buffers per consumer device, but that's a bit involved. Instead, we simply revert to the old behaviour of sharing IOVA mappings when we know that devices can do so (i.e. they share the same IOMMU domain).
Reported-by: Dmitry Osipenko digetx@gmail.com Signed-off-by: Thierry Reding treding@nvidia.com --- drivers/gpu/drm/tegra/gem.c | 10 +++++++- drivers/gpu/drm/tegra/plane.c | 44 ++++++++++++++++++++--------------- drivers/gpu/host1x/job.c | 32 ++++++++++++++++++++++--- 3 files changed, 63 insertions(+), 23 deletions(-)
diff --git a/drivers/gpu/drm/tegra/gem.c b/drivers/gpu/drm/tegra/gem.c index 1237df157e05..623768100c6a 100644 --- a/drivers/gpu/drm/tegra/gem.c +++ b/drivers/gpu/drm/tegra/gem.c @@ -60,8 +60,16 @@ static struct sg_table *tegra_bo_pin(struct device *dev, struct host1x_bo *bo, /* * If we've manually mapped the buffer object through the IOMMU, make * sure to return the IOVA address of our mapping. + * + * Similarly, for buffers that have been allocated by the DMA API the + * physical address can be used for devices that are not attached to + * an IOMMU. For these devices, callers must pass a valid pointer via + * the @phys argument. + * + * Imported buffers were also already mapped at import time, so the + * existing mapping can be reused. */ - if (phys && obj->mm) { + if (phys) { *phys = obj->iova; return NULL; } diff --git a/drivers/gpu/drm/tegra/plane.c b/drivers/gpu/drm/tegra/plane.c index cadcdd9ea427..9ccfb56e9b01 100644 --- a/drivers/gpu/drm/tegra/plane.c +++ b/drivers/gpu/drm/tegra/plane.c @@ -3,6 +3,8 @@ * Copyright (C) 2017 NVIDIA CORPORATION. All rights reserved. */
+#include <linux/iommu.h> + #include <drm/drm_atomic.h> #include <drm/drm_atomic_helper.h> #include <drm/drm_fourcc.h> @@ -107,21 +109,27 @@ const struct drm_plane_funcs tegra_plane_funcs = {
static int tegra_dc_pin(struct tegra_dc *dc, struct tegra_plane_state *state) { + struct iommu_domain *domain = iommu_get_domain_for_dev(dc->dev); unsigned int i; int err;
for (i = 0; i < state->base.fb->format->num_planes; i++) { struct tegra_bo *bo = tegra_fb_get_plane(state->base.fb, i); + dma_addr_t phys_addr, *phys; + struct sg_table *sgt;
- if (!dc->client.group) { - struct sg_table *sgt; + if (!domain || dc->client.group) + phys = &phys_addr; + else + phys = NULL;
- sgt = host1x_bo_pin(dc->dev, &bo->base, NULL); - if (IS_ERR(sgt)) { - err = PTR_ERR(sgt); - goto unpin; - } + sgt = host1x_bo_pin(dc->dev, &bo->base, phys); + if (IS_ERR(sgt)) { + err = PTR_ERR(sgt); + goto unpin; + }
+ if (sgt) { err = dma_map_sg(dc->dev, sgt->sgl, sgt->nents, DMA_TO_DEVICE); if (err == 0) { @@ -143,7 +151,7 @@ static int tegra_dc_pin(struct tegra_dc *dc, struct tegra_plane_state *state) state->iova[i] = sg_dma_address(sgt->sgl); state->sgt[i] = sgt; } else { - state->iova[i] = bo->iova; + state->iova[i] = phys_addr; } }
@@ -156,9 +164,11 @@ static int tegra_dc_pin(struct tegra_dc *dc, struct tegra_plane_state *state) struct tegra_bo *bo = tegra_fb_get_plane(state->base.fb, i); struct sg_table *sgt = state->sgt[i];
- dma_unmap_sg(dc->dev, sgt->sgl, sgt->nents, DMA_TO_DEVICE); - host1x_bo_unpin(dc->dev, &bo->base, sgt); + if (sgt) + dma_unmap_sg(dc->dev, sgt->sgl, sgt->nents, + DMA_TO_DEVICE);
+ host1x_bo_unpin(dc->dev, &bo->base, sgt); state->iova[i] = DMA_MAPPING_ERROR; state->sgt[i] = NULL; } @@ -172,17 +182,13 @@ static void tegra_dc_unpin(struct tegra_dc *dc, struct tegra_plane_state *state)
for (i = 0; i < state->base.fb->format->num_planes; i++) { struct tegra_bo *bo = tegra_fb_get_plane(state->base.fb, i); + struct sg_table *sgt = state->sgt[i];
- if (!dc->client.group) { - struct sg_table *sgt = state->sgt[i]; - - if (sgt) { - dma_unmap_sg(dc->dev, sgt->sgl, sgt->nents, - DMA_TO_DEVICE); - host1x_bo_unpin(dc->dev, &bo->base, sgt); - } - } + if (sgt) + dma_unmap_sg(dc->dev, sgt->sgl, sgt->nents, + DMA_TO_DEVICE);
+ host1x_bo_unpin(dc->dev, &bo->base, sgt); state->iova[i] = DMA_MAPPING_ERROR; state->sgt[i] = NULL; } diff --git a/drivers/gpu/host1x/job.c b/drivers/gpu/host1x/job.c index 60b2fedd0061..8198a4d42c77 100644 --- a/drivers/gpu/host1x/job.c +++ b/drivers/gpu/host1x/job.c @@ -8,6 +8,7 @@ #include <linux/dma-mapping.h> #include <linux/err.h> #include <linux/host1x.h> +#include <linux/iommu.h> #include <linux/kref.h> #include <linux/module.h> #include <linux/scatterlist.h> @@ -101,9 +102,11 @@ static unsigned int pin_job(struct host1x *host, struct host1x_job *job) { struct host1x_client *client = job->client; struct device *dev = client->dev; + struct iommu_domain *domain; unsigned int i; int err;
+ domain = iommu_get_domain_for_dev(dev); job->num_unpins = 0;
for (i = 0; i < job->num_relocs; i++) { @@ -117,7 +120,19 @@ static unsigned int pin_job(struct host1x *host, struct host1x_job *job) goto unpin; }
- if (client->group) + /* + * If the client device is not attached to an IOMMU, the + * physical address of the buffer object can be used. + * + * Similarly, when an IOMMU domain is shared between all + * host1x clients, the IOVA is already available, so no + * need to map the buffer object again. + * + * XXX Note that this isn't always safe to do because it + * relies on an assumption that no cache maintenance is + * needed on the buffer objects. + */ + if (!domain || client->group) phys = &phys_addr; else phys = NULL; @@ -176,6 +191,7 @@ static unsigned int pin_job(struct host1x *host, struct host1x_job *job) dma_addr_t phys_addr; unsigned long shift; struct iova *alloc; + dma_addr_t *phys; unsigned int j;
g->bo = host1x_bo_get(g->bo); @@ -184,7 +200,17 @@ static unsigned int pin_job(struct host1x *host, struct host1x_job *job) goto unpin; }
- sgt = host1x_bo_pin(host->dev, g->bo, NULL); + /** + * If the host1x is not attached to an IOMMU, there is no need + * to map the buffer object for the host1x, since the physical + * address can simply be used. + */ + if (!iommu_get_domain_for_dev(host->dev)) + phys = &phys_addr; + else + phys = NULL; + + sgt = host1x_bo_pin(host->dev, g->bo, phys); if (IS_ERR(sgt)) { err = PTR_ERR(sgt); goto unpin; @@ -214,7 +240,7 @@ static unsigned int pin_job(struct host1x *host, struct host1x_job *job)
job->unpins[job->num_unpins].size = gather_size; phys_addr = iova_dma_addr(&host->iova, alloc); - } else { + } else if (sgt) { err = dma_map_sg(host->dev, sgt->sgl, sgt->nents, DMA_TO_DEVICE); if (!err) {
04.02.2020 16:59, Thierry Reding пишет:
From: Thierry Reding treding@nvidia.com
This partially reverts the DMA API support that was recently merged because it was causing performance regressions on older Tegra devices. Unfortunately, the cache maintenance performed by dma_map_sg() and dma_unmap_sg() causes performance to drop by a factor of 10.
The right solution for this would be to cache mappings for buffers per consumer device, but that's a bit involved. Instead, we simply revert to the old behaviour of sharing IOVA mappings when we know that devices can do so (i.e. they share the same IOMMU domain).
Reported-by: Dmitry Osipenko digetx@gmail.com Signed-off-by: Thierry Reding treding@nvidia.com
drivers/gpu/drm/tegra/gem.c | 10 +++++++- drivers/gpu/drm/tegra/plane.c | 44 ++++++++++++++++++++--------------- drivers/gpu/host1x/job.c | 32 ++++++++++++++++++++++--- 3 files changed, 63 insertions(+), 23 deletions(-)
Tested-by: Dmitry Osipenko digetx@gmail.com
04.02.2020 16:59, Thierry Reding пишет:
From: Thierry Reding treding@nvidia.com
This partially reverts the DMA API support that was recently merged because it was causing performance regressions on older Tegra devices. Unfortunately, the cache maintenance performed by dma_map_sg() and dma_unmap_sg() causes performance to drop by a factor of 10.
The right solution for this would be to cache mappings for buffers per consumer device, but that's a bit involved. Instead, we simply revert to the old behaviour of sharing IOVA mappings when we know that devices can do so (i.e. they share the same IOMMU domain).
Needs a stable tag:
Cc: stable@vger.kernel.org # v5.5
Reported-by: Dmitry Osipenko digetx@gmail.com Signed-off-by: Thierry Reding treding@nvidia.com
drivers/gpu/drm/tegra/gem.c | 10 +++++++- drivers/gpu/drm/tegra/plane.c | 44 ++++++++++++++++++++--------------- drivers/gpu/host1x/job.c | 32 ++++++++++++++++++++++--- 3 files changed, 63 insertions(+), 23 deletions(-)
Otherwise LGTM,
Reviewed-by: Dmitry Osipenko digetx@gmail.com
From: Thierry Reding treding@nvidia.com
The DMA direction is only used by the DMA API, so there is no use in setting it when a buffer object isn't mapped with the DMA API.
Signed-off-by: Thierry Reding treding@nvidia.com --- drivers/gpu/host1x/job.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/gpu/host1x/job.c b/drivers/gpu/host1x/job.c index 8198a4d42c77..a10643aa89aa 100644 --- a/drivers/gpu/host1x/job.c +++ b/drivers/gpu/host1x/job.c @@ -248,6 +248,7 @@ static unsigned int pin_job(struct host1x *host, struct host1x_job *job) goto unpin; }
+ job->unpins[job->num_unpins].dir = DMA_TO_DEVICE; job->unpins[job->num_unpins].dev = host->dev; phys_addr = sg_dma_address(sgt->sgl); } @@ -255,7 +256,6 @@ static unsigned int pin_job(struct host1x *host, struct host1x_job *job) job->addr_phys[job->num_unpins] = phys_addr; job->gather_addr_phys[i] = phys_addr;
- job->unpins[job->num_unpins].dir = DMA_TO_DEVICE; job->unpins[job->num_unpins].bo = g->bo; job->unpins[job->num_unpins].sgt = sgt; job->num_unpins++;
04.02.2020 16:59, Thierry Reding пишет:
From: Thierry Reding treding@nvidia.com
The DMA direction is only used by the DMA API, so there is no use in setting it when a buffer object isn't mapped with the DMA API.
Signed-off-by: Thierry Reding treding@nvidia.com
drivers/gpu/host1x/job.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
Tested-by: Dmitry Osipenko digetx@gmail.com
04.02.2020 16:59, Thierry Reding пишет:
From: Thierry Reding treding@nvidia.com
The DMA direction is only used by the DMA API, so there is no use in setting it when a buffer object isn't mapped with the DMA API.
Signed-off-by: Thierry Reding treding@nvidia.com
drivers/gpu/host1x/job.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/gpu/host1x/job.c b/drivers/gpu/host1x/job.c index 8198a4d42c77..a10643aa89aa 100644 --- a/drivers/gpu/host1x/job.c +++ b/drivers/gpu/host1x/job.c @@ -248,6 +248,7 @@ static unsigned int pin_job(struct host1x *host, struct host1x_job *job) goto unpin; }
}job->unpins[job->num_unpins].dir = DMA_TO_DEVICE; job->unpins[job->num_unpins].dev = host->dev; phys_addr = sg_dma_address(sgt->sgl);
@@ -255,7 +256,6 @@ static unsigned int pin_job(struct host1x *host, struct host1x_job *job) job->addr_phys[job->num_unpins] = phys_addr; job->gather_addr_phys[i] = phys_addr;
job->unpins[job->num_unpins].bo = g->bo; job->unpins[job->num_unpins].sgt = sgt; job->num_unpins++;job->unpins[job->num_unpins].dir = DMA_TO_DEVICE;
Reviewed-by: Dmitry Osipenko digetx@gmail.com
04.02.2020 16:59, Thierry Reding пишет:
From: Thierry Reding treding@nvidia.com
Hi,
this contains a couple of fixes for a DMA API performance regression that was introduced in v5.5 for older Tegra devices. Patches 1 and 2 will likely have to be backported to v5.5.
Thierry
Thierry Reding (3): drm/tegra: Relax IOMMU usage criteria on old Tegra drm/tegra: Reuse IOVA mapping where possible gpu: host1x: Set DMA direction only for DMA-mapped buffer objects
drivers/gpu/drm/tegra/drm.c | 49 +++++++++++++++++++++++------------ drivers/gpu/drm/tegra/gem.c | 10 ++++++- drivers/gpu/drm/tegra/plane.c | 44 +++++++++++++++++-------------- drivers/gpu/host1x/job.c | 34 +++++++++++++++++++++--- 4 files changed, 96 insertions(+), 41 deletions(-)
Works on Tegra20 and Tegra30, thank you very much :)
dri-devel@lists.freedesktop.org