Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 404bfb78 authored by Mikko Perttunen's avatar Mikko Perttunen Committed by Thierry Reding
Browse files

gpu: host1x: Add IOMMU support



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: default avatarMikko Perttunen <mperttunen@nvidia.com>
Signed-off-by: default avatarThierry Reding <treding@nvidia.com>
parent 8cadb01d
Loading
Loading
Loading
Loading
+58 −16
Original line number Original line Diff line number Diff line
@@ -51,9 +51,15 @@ static void host1x_pushbuffer_destroy(struct push_buffer *pb)
	struct host1x_cdma *cdma = pb_to_cdma(pb);
	struct host1x_cdma *cdma = pb_to_cdma(pb);
	struct host1x *host1x = cdma_to_host1x(cdma);
	struct host1x *host1x = cdma_to_host1x(cdma);


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

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


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

	size = pb->size + 4;


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


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

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

		pb->mapped = dma_alloc_wc(host1x->dev, size, &pb->phys,
					  GFP_KERNEL);
					  GFP_KERNEL);
		if (!pb->mapped)
		if (!pb->mapped)
		goto fail;
			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);
	host1x_hw_pushbuffer_init(host1x, pb);


	return 0;
	return 0;


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

	return err;
}
}


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


/*
/*
@@ -111,7 +153,7 @@ 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)
static void host1x_pushbuffer_pop(struct push_buffer *pb, unsigned int slots)
{
{
	/* Advance the next write position */
	/* Advance the next write position */
	pb->fence = (pb->fence + slots * 8) & (pb->size_bytes - 1);
	pb->fence = (pb->fence + slots * 8) & (pb->size - 1);
}
}


/*
/*
@@ -119,7 +161,7 @@ static void host1x_pushbuffer_pop(struct push_buffer *pb, unsigned int slots)
 */
 */
static u32 host1x_pushbuffer_space(struct push_buffer *pb)
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;
}
}


/*
/*
+4 −2
Original line number Original line Diff line number Diff line
@@ -43,10 +43,12 @@ struct host1x_job;


struct push_buffer {
struct push_buffer {
	void *mapped;			/* mapped pushbuffer memory */
	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 fence;			/* index we've written */
	u32 pos;			/* index to write to */
	u32 pos;			/* index to write to */
	u32 size_bytes;
	u32 size;
	u32 alloc_size;
};
};


struct buffer_timeout {
struct buffer_timeout {
+39 −2
Original line number Original line Diff line number Diff line
@@ -27,6 +27,7 @@


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


#include "bus.h"
#include "bus.h"
#include "dev.h"
#include "dev.h"
@@ -168,16 +169,37 @@ static int host1x_probe(struct platform_device *pdev)
		return err;
		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);
	err = host1x_channel_list_init(host);
	if (err) {
	if (err) {
		dev_err(&pdev->dev, "failed to initialize channel list\n");
		dev_err(&pdev->dev, "failed to initialize channel list\n");
		return err;
		goto fail_detach_device;
	}
	}


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


	err = host1x_syncpt_init(host);
	err = host1x_syncpt_init(host);
@@ -206,6 +228,15 @@ static int host1x_probe(struct platform_device *pdev)
	host1x_syncpt_deinit(host);
	host1x_syncpt_deinit(host);
fail_unprepare_disable:
fail_unprepare_disable:
	clk_disable_unprepare(host->clk);
	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;
	return err;
}
}


@@ -218,6 +249,12 @@ static int host1x_remove(struct platform_device *pdev)
	host1x_syncpt_deinit(host);
	host1x_syncpt_deinit(host);
	clk_disable_unprepare(host->clk);
	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;
	return 0;
}
}


+6 −0
Original line number Original line Diff line number Diff line
@@ -19,6 +19,8 @@


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


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


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

	struct mutex intr_mutex;
	struct mutex intr_mutex;
	int intr_syncpt_irq;
	int intr_syncpt_irq;


+7 −9
Original line number Original line Diff line number Diff line
@@ -30,7 +30,7 @@
 */
 */
static void push_buffer_init(struct push_buffer *pb)
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);
}
}


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


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


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


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


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


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