Skip to content

Commit

Permalink
gpu: host1x: Add IOMMU support
Browse files Browse the repository at this point in the history
Add support for the Host1x unit to be located behind
an IOMMU. This is required when gather buffers may be
allocated non-contiguously in physical memory, as can
be the case when TegraDRM is also using the IOMMU.

Signed-off-by: Mikko Perttunen <mperttunen@nvidia.com>
Signed-off-by: Thierry Reding <treding@nvidia.com>
  • Loading branch information
Mikko Perttunen authored and Thierry Reding committed Apr 5, 2017
1 parent 8cadb01 commit 404bfb7
Show file tree
Hide file tree
Showing 7 changed files with 177 additions and 39 deletions.
74 changes: 58 additions & 16 deletions drivers/gpu/host1x/cdma.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,15 @@ static void host1x_pushbuffer_destroy(struct push_buffer *pb)
struct host1x_cdma *cdma = pb_to_cdma(pb);
struct host1x *host1x = cdma_to_host1x(cdma);

if (pb->phys != 0)
dma_free_wc(host1x->dev, pb->size_bytes + 4, pb->mapped,
pb->phys);
if (!pb->phys)
return;

if (host1x->domain) {
iommu_unmap(host1x->domain, pb->dma, pb->alloc_size);
free_iova(&host1x->iova, iova_pfn(&host1x->iova, pb->dma));
}

dma_free_wc(host1x->dev, pb->alloc_size, pb->mapped, pb->phys);

pb->mapped = NULL;
pb->phys = 0;
Expand All @@ -66,28 +72,64 @@ static int host1x_pushbuffer_init(struct push_buffer *pb)
{
struct host1x_cdma *cdma = pb_to_cdma(pb);
struct host1x *host1x = cdma_to_host1x(cdma);
struct iova *alloc;
u32 size;
int err;

pb->mapped = NULL;
pb->phys = 0;
pb->size_bytes = HOST1X_PUSHBUFFER_SLOTS * 8;
pb->size = HOST1X_PUSHBUFFER_SLOTS * 8;

size = pb->size + 4;

/* initialize buffer pointers */
pb->fence = pb->size_bytes - 8;
pb->fence = pb->size - 8;
pb->pos = 0;

/* allocate and map pushbuffer memory */
pb->mapped = dma_alloc_wc(host1x->dev, pb->size_bytes + 4, &pb->phys,
GFP_KERNEL);
if (!pb->mapped)
goto fail;
if (host1x->domain) {
unsigned long shift;

size = iova_align(&host1x->iova, size);

pb->mapped = dma_alloc_wc(host1x->dev, size, &pb->phys,
GFP_KERNEL);
if (!pb->mapped)
return -ENOMEM;

shift = iova_shift(&host1x->iova);
alloc = alloc_iova(&host1x->iova, size >> shift,
host1x->iova_end >> shift, true);
if (!alloc) {
err = -ENOMEM;
goto iommu_free_mem;
}

pb->dma = iova_dma_addr(&host1x->iova, alloc);
err = iommu_map(host1x->domain, pb->dma, pb->phys, size,
IOMMU_READ);
if (err)
goto iommu_free_iova;
} else {
pb->mapped = dma_alloc_wc(host1x->dev, size, &pb->phys,
GFP_KERNEL);
if (!pb->mapped)
return -ENOMEM;

pb->dma = pb->phys;
}

pb->alloc_size = size;

host1x_hw_pushbuffer_init(host1x, pb);

return 0;

fail:
host1x_pushbuffer_destroy(pb);
return -ENOMEM;
iommu_free_iova:
__free_iova(&host1x->iova, alloc);
iommu_free_mem:
dma_free_wc(host1x->dev, pb->alloc_size, pb->mapped, pb->phys);

return err;
}

/*
Expand All @@ -101,7 +143,7 @@ static void host1x_pushbuffer_push(struct push_buffer *pb, u32 op1, u32 op2)
WARN_ON(pb->pos == pb->fence);
*(p++) = op1;
*(p++) = op2;
pb->pos = (pb->pos + 8) & (pb->size_bytes - 1);
pb->pos = (pb->pos + 8) & (pb->size - 1);
}

/*
Expand All @@ -111,15 +153,15 @@ static void host1x_pushbuffer_push(struct push_buffer *pb, u32 op1, u32 op2)
static void host1x_pushbuffer_pop(struct push_buffer *pb, unsigned int slots)
{
/* Advance the next write position */
pb->fence = (pb->fence + slots * 8) & (pb->size_bytes - 1);
pb->fence = (pb->fence + slots * 8) & (pb->size - 1);
}

/*
* Return the number of two word slots free in the push buffer
*/
static u32 host1x_pushbuffer_space(struct push_buffer *pb)
{
return ((pb->fence - pb->pos) & (pb->size_bytes - 1)) / 8;
return ((pb->fence - pb->pos) & (pb->size - 1)) / 8;
}

/*
Expand Down
6 changes: 4 additions & 2 deletions drivers/gpu/host1x/cdma.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,12 @@ struct host1x_job;

struct push_buffer {
void *mapped; /* mapped pushbuffer memory */
dma_addr_t phys; /* physical address of pushbuffer */
dma_addr_t dma; /* device address of pushbuffer */
phys_addr_t phys; /* physical address of pushbuffer */
u32 fence; /* index we've written */
u32 pos; /* index to write to */
u32 size_bytes;
u32 size;
u32 alloc_size;
};

struct buffer_timeout {
Expand Down
41 changes: 39 additions & 2 deletions drivers/gpu/host1x/dev.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

#define CREATE_TRACE_POINTS
#include <trace/events/host1x.h>
#undef CREATE_TRACE_POINTS

#include "bus.h"
#include "dev.h"
Expand Down Expand Up @@ -168,16 +169,37 @@ static int host1x_probe(struct platform_device *pdev)
return err;
}

if (iommu_present(&platform_bus_type)) {
struct iommu_domain_geometry *geometry;
unsigned long order;

host->domain = iommu_domain_alloc(&platform_bus_type);
if (!host->domain)
return -ENOMEM;

err = iommu_attach_device(host->domain, &pdev->dev);
if (err)
goto fail_free_domain;

geometry = &host->domain->geometry;

order = __ffs(host->domain->pgsize_bitmap);
init_iova_domain(&host->iova, 1UL << order,
geometry->aperture_start >> order,
geometry->aperture_end >> order);
host->iova_end = geometry->aperture_end;
}

err = host1x_channel_list_init(host);
if (err) {
dev_err(&pdev->dev, "failed to initialize channel list\n");
return err;
goto fail_detach_device;
}

err = clk_prepare_enable(host->clk);
if (err < 0) {
dev_err(&pdev->dev, "failed to enable clock\n");
return err;
goto fail_detach_device;
}

err = host1x_syncpt_init(host);
Expand Down Expand Up @@ -206,6 +228,15 @@ static int host1x_probe(struct platform_device *pdev)
host1x_syncpt_deinit(host);
fail_unprepare_disable:
clk_disable_unprepare(host->clk);
fail_detach_device:
if (host->domain) {
put_iova_domain(&host->iova);
iommu_detach_device(host->domain, &pdev->dev);
}
fail_free_domain:
if (host->domain)
iommu_domain_free(host->domain);

return err;
}

Expand All @@ -218,6 +249,12 @@ static int host1x_remove(struct platform_device *pdev)
host1x_syncpt_deinit(host);
clk_disable_unprepare(host->clk);

if (host->domain) {
put_iova_domain(&host->iova);
iommu_detach_device(host->domain, &pdev->dev);
iommu_domain_free(host->domain);
}

return 0;
}

Expand Down
6 changes: 6 additions & 0 deletions drivers/gpu/host1x/dev.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

#include <linux/platform_device.h>
#include <linux/device.h>
#include <linux/iommu.h>
#include <linux/iova.h>

#include "channel.h"
#include "syncpt.h"
Expand Down Expand Up @@ -108,6 +110,10 @@ struct host1x {
struct device *dev;
struct clk *clk;

struct iommu_domain *domain;
struct iova_domain iova;
dma_addr_t iova_end;

struct mutex intr_mutex;
int intr_syncpt_irq;

Expand Down
16 changes: 7 additions & 9 deletions drivers/gpu/host1x/hw/cdma_hw.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
*/
static void push_buffer_init(struct push_buffer *pb)
{
*(u32 *)(pb->mapped + pb->size_bytes) = host1x_opcode_restart(0);
*(u32 *)(pb->mapped + pb->size) = host1x_opcode_restart(0);
}

/*
Expand All @@ -55,8 +55,8 @@ static void cdma_timeout_cpu_incr(struct host1x_cdma *cdma, u32 getptr,
*(p++) = HOST1X_OPCODE_NOP;
*(p++) = HOST1X_OPCODE_NOP;
dev_dbg(host1x->dev, "%s: NOP at %pad+%#x\n", __func__,
&pb->phys, getptr);
getptr = (getptr + 8) & (pb->size_bytes - 1);
&pb->dma, getptr);
getptr = (getptr + 8) & (pb->size - 1);
}

wmb();
Expand All @@ -78,10 +78,9 @@ static void cdma_start(struct host1x_cdma *cdma)
HOST1X_CHANNEL_DMACTRL);

/* set base, put and end pointer */
host1x_ch_writel(ch, cdma->push_buffer.phys, HOST1X_CHANNEL_DMASTART);
host1x_ch_writel(ch, cdma->push_buffer.dma, HOST1X_CHANNEL_DMASTART);
host1x_ch_writel(ch, cdma->push_buffer.pos, HOST1X_CHANNEL_DMAPUT);
host1x_ch_writel(ch, cdma->push_buffer.phys +
cdma->push_buffer.size_bytes + 4,
host1x_ch_writel(ch, cdma->push_buffer.dma + cdma->push_buffer.size + 4,
HOST1X_CHANNEL_DMAEND);

/* reset GET */
Expand Down Expand Up @@ -115,9 +114,8 @@ static void cdma_timeout_restart(struct host1x_cdma *cdma, u32 getptr)
HOST1X_CHANNEL_DMACTRL);

/* set base, end pointer (all of memory) */
host1x_ch_writel(ch, cdma->push_buffer.phys, HOST1X_CHANNEL_DMASTART);
host1x_ch_writel(ch, cdma->push_buffer.phys +
cdma->push_buffer.size_bytes,
host1x_ch_writel(ch, cdma->push_buffer.dma, HOST1X_CHANNEL_DMASTART);
host1x_ch_writel(ch, cdma->push_buffer.dma + cdma->push_buffer.size,
HOST1X_CHANNEL_DMAEND);

/* set GET, by loading the value in PUT (then reset GET) */
Expand Down
Loading

0 comments on commit 404bfb7

Please sign in to comment.