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

Commit 4a525bad authored by Dave Airlie's avatar Dave Airlie
Browse files

Merge tag 'drm/tegra/for-4.13-rc1' of git://anongit.freedesktop.org/tegra/linux into drm-next

drm/tegra: Changes for v4.13-rc1

This starts off with the addition of more documentation for the host1x
and DRM drivers and finishes with a slew of fixes and enhancements for
the staging IOCTLs as a result of the awesome work done by Dmitry and
Erik on the grate reverse-engineering effort.

* tag 'drm/tegra/for-4.13-rc1' of git://anongit.freedesktop.org/tegra/linux:
  gpu: host1x: At first try a non-blocking allocation for the gather copy
  gpu: host1x: Refactor channel allocation code
  gpu: host1x: Remove unused host1x_cdma_stop() definition
  gpu: host1x: Remove unused 'struct host1x_cmdbuf'
  gpu: host1x: Check waits in the firewall
  gpu: host1x: Correct swapped arguments in the is_addr_reg() definition
  gpu: host1x: Forbid unrelated SETCLASS opcode in the firewall
  gpu: host1x: Forbid RESTART opcode in the firewall
  gpu: host1x: Forbid relocation address shifting in the firewall
  gpu: host1x: Do not leak BO's phys address to userspace
  gpu: host1x: Correct host1x_job_pin() error handling
  gpu: host1x: Initialize firewall class to the job's one
  drm/tegra: dc: Disable plane if it is invisible
  drm/tegra: dc: Apply clipping to the plane
  drm/tegra: dc: Avoid reset asserts on Tegra20
  drm/tegra: Check syncpoint ID in the 'submit' IOCTL
  drm/tegra: Correct copying of waitchecks and disable them in the 'submit' IOCTL
  drm/tegra: Check for malformed offsets and sizes in the 'submit' IOCTL
  drm/tegra: Add driver documentation
  gpu: host1x: Flesh out kerneldoc
parents 925344cc 43240bbd
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -13,6 +13,7 @@ Linux GPU Driver Developer's Guide
   i915
   meson
   pl111
   tegra
   tinydrm
   vc4
   vga-switcheroo
+178 −0
Original line number Diff line number Diff line
===============================================
 drm/tegra NVIDIA Tegra GPU and display driver
===============================================

NVIDIA Tegra SoCs support a set of display, graphics and video functions via
the host1x controller. host1x supplies command streams, gathered from a push
buffer provided directly by the CPU, to its clients via channels. Software,
or blocks amongst themselves, can use syncpoints for synchronization.

Up until, but not including, Tegra124 (aka Tegra K1) the drm/tegra driver
supports the built-in GPU, comprised of the gr2d and gr3d engines. Starting
with Tegra124 the GPU is based on the NVIDIA desktop GPU architecture and
supported by the drm/nouveau driver.

The drm/tegra driver supports NVIDIA Tegra SoC generations since Tegra20. It
has three parts:

  - A host1x driver that provides infrastructure and access to the host1x
    services.

  - A KMS driver that supports the display controllers as well as a number of
    outputs, such as RGB, HDMI, DSI, and DisplayPort.

  - A set of custom userspace IOCTLs that can be used to submit jobs to the
    GPU and video engines via host1x.

Driver Infrastructure
=====================

The various host1x clients need to be bound together into a logical device in
order to expose their functionality to users. The infrastructure that supports
this is implemented in the host1x driver. When a driver is registered with the
infrastructure it provides a list of compatible strings specifying the devices
that it needs. The infrastructure creates a logical device and scan the device
tree for matching device nodes, adding the required clients to a list. Drivers
for individual clients register with the infrastructure as well and are added
to the logical host1x device.

Once all clients are available, the infrastructure will initialize the logical
device using a driver-provided function which will set up the bits specific to
the subsystem and in turn initialize each of its clients.

Similarly, when one of the clients is unregistered, the infrastructure will
destroy the logical device by calling back into the driver, which ensures that
the subsystem specific bits are torn down and the clients destroyed in turn.

Host1x Infrastructure Reference
-------------------------------

.. kernel-doc:: include/linux/host1x.h

.. kernel-doc:: drivers/gpu/host1x/bus.c
   :export:

Host1x Syncpoint Reference
--------------------------

.. kernel-doc:: drivers/gpu/host1x/syncpt.c
   :export:

KMS driver
==========

The display hardware has remained mostly backwards compatible over the various
Tegra SoC generations, up until Tegra186 which introduces several changes that
make it difficult to support with a parameterized driver.

Display Controllers
-------------------

Tegra SoCs have two display controllers, each of which can be associated with
zero or more outputs. Outputs can also share a single display controller, but
only if they run with compatible display timings. Two display controllers can
also share a single framebuffer, allowing cloned configurations even if modes
on two outputs don't match. A display controller is modelled as a CRTC in KMS
terms.

On Tegra186, the number of display controllers has been increased to three. A
display controller can no longer drive all of the outputs. While two of these
controllers can drive both DSI outputs and both SOR outputs, the third cannot
drive any DSI.

Windows
~~~~~~~

A display controller controls a set of windows that can be used to composite
multiple buffers onto the screen. While it is possible to assign arbitrary Z
ordering to individual windows (by programming the corresponding blending
registers), this is currently not supported by the driver. Instead, it will
assume a fixed Z ordering of the windows (window A is the root window, that
is, the lowest, while windows B and C are overlaid on top of window A). The
overlay windows support multiple pixel formats and can automatically convert
from YUV to RGB at scanout time. This makes them useful for displaying video
content. In KMS, each window is modelled as a plane. Each display controller
has a hardware cursor that is exposed as a cursor plane.

Outputs
-------

The type and number of supported outputs varies between Tegra SoC generations.
All generations support at least HDMI. While earlier generations supported the
very simple RGB interfaces (one per display controller), recent generations no
longer do and instead provide standard interfaces such as DSI and eDP/DP.

Outputs are modelled as a composite encoder/connector pair.

RGB/LVDS
~~~~~~~~

This interface is no longer available since Tegra124. It has been replaced by
the more standard DSI and eDP interfaces.

HDMI
~~~~

HDMI is supported on all Tegra SoCs. Starting with Tegra210, HDMI is provided
by the versatile SOR output, which supports eDP, DP and HDMI. The SOR is able
to support HDMI 2.0, though support for this is currently not merged.

DSI
~~~

Although Tegra has supported DSI since Tegra30, the controller has changed in
several ways in Tegra114. Since none of the publicly available development
boards prior to Dalmore (Tegra114) have made use of DSI, only Tegra114 and
later are supported by the drm/tegra driver.

eDP/DP
~~~~~~

eDP was first introduced in Tegra124 where it was used to drive the display
panel for notebook form factors. Tegra210 added support for full DisplayPort
support, though this is currently not implemented in the drm/tegra driver.

Userspace Interface
===================

The userspace interface provided by drm/tegra allows applications to create
GEM buffers, access and control syncpoints as well as submit command streams
to host1x.

GEM Buffers
-----------

The ``DRM_IOCTL_TEGRA_GEM_CREATE`` IOCTL is used to create a GEM buffer object
with Tegra-specific flags. This is useful for buffers that should be tiled, or
that are to be scanned out upside down (useful for 3D content).

After a GEM buffer object has been created, its memory can be mapped by an
application using the mmap offset returned by the ``DRM_IOCTL_TEGRA_GEM_MMAP``
IOCTL.

Syncpoints
----------

The current value of a syncpoint can be obtained by executing the
``DRM_IOCTL_TEGRA_SYNCPT_READ`` IOCTL. Incrementing the syncpoint is achieved
using the ``DRM_IOCTL_TEGRA_SYNCPT_INCR`` IOCTL.

Userspace can also request blocking on a syncpoint. To do so, it needs to
execute the ``DRM_IOCTL_TEGRA_SYNCPT_WAIT`` IOCTL, specifying the value of
the syncpoint to wait for. The kernel will release the application when the
syncpoint reaches that value or after a specified timeout.

Command Stream Submission
-------------------------

Before an application can submit command streams to host1x it needs to open a
channel to an engine using the ``DRM_IOCTL_TEGRA_OPEN_CHANNEL`` IOCTL. Client
IDs are used to identify the target of the channel. When a channel is no
longer needed, it can be closed using the ``DRM_IOCTL_TEGRA_CLOSE_CHANNEL``
IOCTL. To retrieve the syncpoint associated with a channel, an application
can use the ``DRM_IOCTL_TEGRA_GET_SYNCPT``.

After opening a channel, submitting command streams is easy. The application
writes commands into the memory backing a GEM buffer object and passes these
to the ``DRM_IOCTL_TEGRA_SUBMIT`` IOCTL along with various other parameters,
such as the syncpoints or relocations used in the job submission.
+63 −29
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ struct tegra_dc_soc_info {
	bool supports_block_linear;
	unsigned int pitch_align;
	bool has_powergate;
	bool broken_reset;
};

struct tegra_plane {
@@ -485,12 +486,25 @@ static int tegra_plane_state_add(struct tegra_plane *plane,
{
	struct drm_crtc_state *crtc_state;
	struct tegra_dc_state *tegra;
	struct drm_rect clip;
	int err;

	/* Propagate errors from allocation or locking failures. */
	crtc_state = drm_atomic_get_crtc_state(state->state, state->crtc);
	if (IS_ERR(crtc_state))
		return PTR_ERR(crtc_state);

	clip.x1 = 0;
	clip.y1 = 0;
	clip.x2 = crtc_state->mode.hdisplay;
	clip.y2 = crtc_state->mode.vdisplay;

	/* Check plane state for visibility and calculate clipping bounds */
	err = drm_plane_helper_check_state(state, &clip, 0, INT_MAX,
					   true, true);
	if (err < 0)
		return err;

	tegra = to_dc_state(crtc_state);

	tegra->planes |= WIN_A_ACT_REQ << plane->index;
@@ -545,6 +559,23 @@ static int tegra_plane_atomic_check(struct drm_plane *plane,
	return 0;
}

static void tegra_dc_disable_window(struct tegra_dc *dc, int index)
{
	unsigned long flags;
	u32 value;

	spin_lock_irqsave(&dc->lock, flags);

	value = WINDOW_A_SELECT << index;
	tegra_dc_writel(dc, value, DC_CMD_DISPLAY_WINDOW_HEADER);

	value = tegra_dc_readl(dc, DC_WIN_WIN_OPTIONS);
	value &= ~WIN_ENABLE;
	tegra_dc_writel(dc, value, DC_WIN_WIN_OPTIONS);

	spin_unlock_irqrestore(&dc->lock, flags);
}

static void tegra_plane_atomic_update(struct drm_plane *plane,
				      struct drm_plane_state *old_state)
{
@@ -559,15 +590,18 @@ static void tegra_plane_atomic_update(struct drm_plane *plane,
	if (!plane->state->crtc || !plane->state->fb)
		return;

	if (!plane->state->visible)
		return tegra_dc_disable_window(dc, p->index);

	memset(&window, 0, sizeof(window));
	window.src.x = plane->state->src_x >> 16;
	window.src.y = plane->state->src_y >> 16;
	window.src.w = plane->state->src_w >> 16;
	window.src.h = plane->state->src_h >> 16;
	window.dst.x = plane->state->crtc_x;
	window.dst.y = plane->state->crtc_y;
	window.dst.w = plane->state->crtc_w;
	window.dst.h = plane->state->crtc_h;
	window.src.x = plane->state->src.x1 >> 16;
	window.src.y = plane->state->src.y1 >> 16;
	window.src.w = drm_rect_width(&plane->state->src) >> 16;
	window.src.h = drm_rect_height(&plane->state->src) >> 16;
	window.dst.x = plane->state->dst.x1;
	window.dst.y = plane->state->dst.y1;
	window.dst.w = drm_rect_width(&plane->state->dst);
	window.dst.h = drm_rect_height(&plane->state->dst);
	window.bits_per_pixel = fb->format->cpp[0] * 8;
	window.bottom_up = tegra_fb_is_bottom_up(fb);

@@ -598,8 +632,6 @@ static void tegra_plane_atomic_disable(struct drm_plane *plane,
{
	struct tegra_plane *p = to_tegra_plane(plane);
	struct tegra_dc *dc;
	unsigned long flags;
	u32 value;

	/* rien ne va plus */
	if (!old_state || !old_state->crtc)
@@ -607,16 +639,7 @@ static void tegra_plane_atomic_disable(struct drm_plane *plane,

	dc = to_tegra_dc(old_state->crtc);

	spin_lock_irqsave(&dc->lock, flags);

	value = WINDOW_A_SELECT << p->index;
	tegra_dc_writel(dc, value, DC_CMD_DISPLAY_WINDOW_HEADER);

	value = tegra_dc_readl(dc, DC_WIN_WIN_OPTIONS);
	value &= ~WIN_ENABLE;
	tegra_dc_writel(dc, value, DC_WIN_WIN_OPTIONS);

	spin_unlock_irqrestore(&dc->lock, flags);
	tegra_dc_disable_window(dc, p->index);
}

static const struct drm_plane_helper_funcs tegra_primary_plane_helper_funcs = {
@@ -1856,6 +1879,7 @@ static const struct tegra_dc_soc_info tegra20_dc_soc_info = {
	.supports_block_linear = false,
	.pitch_align = 8,
	.has_powergate = false,
	.broken_reset = true,
};

static const struct tegra_dc_soc_info tegra30_dc_soc_info = {
@@ -1865,6 +1889,7 @@ static const struct tegra_dc_soc_info tegra30_dc_soc_info = {
	.supports_block_linear = false,
	.pitch_align = 8,
	.has_powergate = false,
	.broken_reset = false,
};

static const struct tegra_dc_soc_info tegra114_dc_soc_info = {
@@ -1874,6 +1899,7 @@ static const struct tegra_dc_soc_info tegra114_dc_soc_info = {
	.supports_block_linear = false,
	.pitch_align = 64,
	.has_powergate = true,
	.broken_reset = false,
};

static const struct tegra_dc_soc_info tegra124_dc_soc_info = {
@@ -1883,6 +1909,7 @@ static const struct tegra_dc_soc_info tegra124_dc_soc_info = {
	.supports_block_linear = true,
	.pitch_align = 64,
	.has_powergate = true,
	.broken_reset = false,
};

static const struct tegra_dc_soc_info tegra210_dc_soc_info = {
@@ -1892,6 +1919,7 @@ static const struct tegra_dc_soc_info tegra210_dc_soc_info = {
	.supports_block_linear = true,
	.pitch_align = 64,
	.has_powergate = true,
	.broken_reset = false,
};

static const struct of_device_id tegra_dc_of_match[] = {
@@ -1989,6 +2017,7 @@ static int tegra_dc_probe(struct platform_device *pdev)
		return PTR_ERR(dc->rst);
	}

	if (!dc->soc->broken_reset)
		reset_control_assert(dc->rst);

	if (dc->soc->has_powergate) {
@@ -2063,11 +2092,13 @@ static int tegra_dc_suspend(struct device *dev)
	struct tegra_dc *dc = dev_get_drvdata(dev);
	int err;

	if (!dc->soc->broken_reset) {
		err = reset_control_assert(dc->rst);
		if (err < 0) {
			dev_err(dev, "failed to assert reset: %d\n", err);
			return err;
		}
	}

	if (dc->soc->has_powergate)
		tegra_powergate_power_off(dc->powergate);
@@ -2096,12 +2127,15 @@ static int tegra_dc_resume(struct device *dev)
			return err;
		}

		if (!dc->soc->broken_reset) {
			err = reset_control_deassert(dc->rst);
			if (err < 0) {
			dev_err(dev, "failed to deassert reset: %d\n", err);
				dev_err(dev,
					"failed to deassert reset: %d\n", err);
				return err;
			}
		}
	}

	return 0;
}
+115 −4
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@
#define DRIVER_PATCHLEVEL 0

#define CARVEOUT_SZ SZ_64M
#define CDMA_GATHER_FETCHES_MAX_NB 16383

struct tegra_drm_file {
	struct idr contexts;
@@ -348,6 +349,36 @@ static int host1x_reloc_copy_from_user(struct host1x_reloc *dest,
	return 0;
}

static int host1x_waitchk_copy_from_user(struct host1x_waitchk *dest,
					 struct drm_tegra_waitchk __user *src,
					 struct drm_file *file)
{
	u32 cmdbuf;
	int err;

	err = get_user(cmdbuf, &src->handle);
	if (err < 0)
		return err;

	err = get_user(dest->offset, &src->offset);
	if (err < 0)
		return err;

	err = get_user(dest->syncpt_id, &src->syncpt);
	if (err < 0)
		return err;

	err = get_user(dest->thresh, &src->thresh);
	if (err < 0)
		return err;

	dest->bo = host1x_bo_lookup(file, cmdbuf);
	if (!dest->bo)
		return -ENOENT;

	return 0;
}

int tegra_drm_submit(struct tegra_drm_context *context,
		     struct drm_tegra_submit *args, struct drm_device *drm,
		     struct drm_file *file)
@@ -362,6 +393,8 @@ int tegra_drm_submit(struct tegra_drm_context *context,
	struct drm_tegra_waitchk __user *waitchks =
		(void __user *)(uintptr_t)args->waitchks;
	struct drm_tegra_syncpt syncpt;
	struct host1x *host1x = dev_get_drvdata(drm->dev->parent);
	struct host1x_syncpt *sp;
	struct host1x_job *job;
	int err;

@@ -369,6 +402,10 @@ int tegra_drm_submit(struct tegra_drm_context *context,
	if (args->num_syncpts != 1)
		return -EINVAL;

	/* We don't yet support waitchks */
	if (args->num_waitchks != 0)
		return -EINVAL;

	job = host1x_job_alloc(context->channel, args->num_cmdbufs,
			       args->num_relocs, args->num_waitchks);
	if (!job)
@@ -383,18 +420,42 @@ int tegra_drm_submit(struct tegra_drm_context *context,
	while (num_cmdbufs) {
		struct drm_tegra_cmdbuf cmdbuf;
		struct host1x_bo *bo;
		struct tegra_bo *obj;
		u64 offset;

		if (copy_from_user(&cmdbuf, cmdbufs, sizeof(cmdbuf))) {
			err = -EFAULT;
			goto fail;
		}

		/*
		 * The maximum number of CDMA gather fetches is 16383, a higher
		 * value means the words count is malformed.
		 */
		if (cmdbuf.words > CDMA_GATHER_FETCHES_MAX_NB) {
			err = -EINVAL;
			goto fail;
		}

		bo = host1x_bo_lookup(file, cmdbuf.handle);
		if (!bo) {
			err = -ENOENT;
			goto fail;
		}

		offset = (u64)cmdbuf.offset + (u64)cmdbuf.words * sizeof(u32);
		obj = host1x_to_tegra_bo(bo);

		/*
		 * Gather buffer base address must be 4-bytes aligned,
		 * unaligned offset is malformed and cause commands stream
		 * corruption on the buffer address relocation.
		 */
		if (offset & 3 || offset >= obj->gem.size) {
			err = -EINVAL;
			goto fail;
		}

		host1x_job_add_gather(job, bo, cmdbuf.words, cmdbuf.offset);
		num_cmdbufs--;
		cmdbufs++;
@@ -402,18 +463,60 @@ int tegra_drm_submit(struct tegra_drm_context *context,

	/* copy and resolve relocations from submit */
	while (num_relocs--) {
		struct host1x_reloc *reloc;
		struct tegra_bo *obj;

		err = host1x_reloc_copy_from_user(&job->relocarray[num_relocs],
						  &relocs[num_relocs], drm,
						  file);
		if (err < 0)
			goto fail;

		reloc = &job->relocarray[num_relocs];
		obj = host1x_to_tegra_bo(reloc->cmdbuf.bo);

		/*
		 * The unaligned cmdbuf offset will cause an unaligned write
		 * during of the relocations patching, corrupting the commands
		 * stream.
		 */
		if (reloc->cmdbuf.offset & 3 ||
		    reloc->cmdbuf.offset >= obj->gem.size) {
			err = -EINVAL;
			goto fail;
		}

	if (copy_from_user(job->waitchk, waitchks,
			   sizeof(*waitchks) * num_waitchks)) {
		err = -EFAULT;
		obj = host1x_to_tegra_bo(reloc->target.bo);

		if (reloc->target.offset >= obj->gem.size) {
			err = -EINVAL;
			goto fail;
		}
	}

	/* copy and resolve waitchks from submit */
	while (num_waitchks--) {
		struct host1x_waitchk *wait = &job->waitchk[num_waitchks];
		struct tegra_bo *obj;

		err = host1x_waitchk_copy_from_user(wait,
						    &waitchks[num_waitchks],
						    file);
		if (err < 0)
			goto fail;

		obj = host1x_to_tegra_bo(wait->bo);

		/*
		 * The unaligned offset will cause an unaligned write during
		 * of the waitchks patching, corrupting the commands stream.
		 */
		if (wait->offset & 3 ||
		    wait->offset >= obj->gem.size) {
			err = -EINVAL;
			goto fail;
		}
	}

	if (copy_from_user(&syncpt, (void __user *)(uintptr_t)args->syncpts,
			   sizeof(syncpt))) {
@@ -421,7 +524,15 @@ int tegra_drm_submit(struct tegra_drm_context *context,
		goto fail;
	}

	/* check whether syncpoint ID is valid */
	sp = host1x_syncpt_get(host1x, syncpt.id);
	if (!sp) {
		err = -ENOENT;
		goto fail;
	}

	job->is_addr_reg = context->client->ops->is_addr_reg;
	job->is_valid_class = context->client->ops->is_valid_class;
	job->syncpt_incrs = syncpt.incrs;
	job->syncpt_id = syncpt.id;
	job->timeout = 10000;
+1 −0
Original line number Diff line number Diff line
@@ -83,6 +83,7 @@ struct tegra_drm_client_ops {
			    struct tegra_drm_context *context);
	void (*close_channel)(struct tegra_drm_context *context);
	int (*is_addr_reg)(struct device *dev, u32 class, u32 offset);
	int (*is_valid_class)(u32 class);
	int (*submit)(struct tegra_drm_context *context,
		      struct drm_tegra_submit *args, struct drm_device *drm,
		      struct drm_file *file);
Loading