All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC 0/4] drm/msm/mdp5: writeback connector support
@ 2018-02-23 13:17 Rob Clark
       [not found] ` <20180223131758.18362-1-robdclark-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
  0 siblings, 1 reply; 41+ messages in thread
From: Rob Clark @ 2018-02-23 13:17 UTC (permalink / raw)
  To: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW
  Cc: linux-arm-msm-u79uwXL29TY76Z2rM5mHXA, Liviu Dudau,
	freedreno-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, Brian Starkey,
	Rob Clark

(Sorry, meant to send this earlier, but got distracted on other things)

The first two patches are Brian Starkey's earlier writeback-connector
patches, with very minor rebasing to drm-next/v4.16-rc1, plus one small
addition to add atomic_commit() vfunc to the connector helpers, so that
writeback jobs could be committed directly from the atomic helpers.

I've tested this on dragonboard 410c (apq8016), with a hacked up
kmscube[1] to use the output of writeback as src texture for the next
frame.  (Yes, I know this isn't a "real" userspace, but I needed some
test code.)

So far, I've only managed to get WB2, which is connected to LM3, to
work.  I think there should be a WB0 attached to LM0 (which is also
attached to the DSI INTF).  It isn't clear to me if this can be used at
the same time as DSI video mode output.  If that is possible, then we
could support attaching both DSI and WB encoder+connector to the same
CRTC and re-use all the same planes being used for scanout to flatten
the composited image to a single buffer.  This would be an obvious and
simple use-case for weston and drm-hwc, to reduce power/bandwidth when
the screen is not updating by flattening all layers into a single layer
for the next frame.  But my attempts at making this work just made the
hardware grumpy.

These patches apply on top of msm-next[2] (which contains a couple other
required fixes, in particular improved CTL START signal handling).  The
patches can also be found on the msm-next-writeback[3] branch.

[1] https://github.com/robclark/kmscube/commits/writeback
[2] https://cgit.freedesktop.org/~robclark/linux/log/?h=msm-next
[3] https://cgit.freedesktop.org/~robclark/linux/log/?h=msm-next-writeback

Brian Starkey (2):
  drm: Add writeback connector type
  drm: writeback: Add out-fences for writeback connectors

Rob Clark (2):
  drm/msm/mdp5: add config for writeback pipes
  drm/msm/mdp5: writeback support

 Documentation/gpu/drm-kms.rst             |   9 +
 drivers/gpu/drm/Makefile                  |   2 +-
 drivers/gpu/drm/drm_atomic.c              | 229 ++++++++++++++++++-
 drivers/gpu/drm/drm_atomic_helper.c       |  30 +++
 drivers/gpu/drm/drm_connector.c           |   4 +-
 drivers/gpu/drm/drm_writeback.c           | 362 +++++++++++++++++++++++++++++
 drivers/gpu/drm/msm/Makefile              |   1 +
 drivers/gpu/drm/msm/disp/mdp5/mdp5.xml.h  |   2 -
 drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.c  |  17 +-
 drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.h  |  11 +
 drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c |  23 +-
 drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c  |  39 +++-
 drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.h  |   7 +-
 drivers/gpu/drm/msm/disp/mdp5/mdp5_wb.c   | 367 ++++++++++++++++++++++++++++++
 drivers/gpu/drm/msm/dsi/dsi_host.c        |   4 +-
 include/drm/drm_atomic.h                  |  11 +
 include/drm/drm_connector.h               |  13 ++
 include/drm/drm_mode_config.h             |  22 ++
 include/drm/drm_modeset_helper_vtables.h  |  11 +
 include/drm/drm_writeback.h               | 128 +++++++++++
 include/uapi/drm/drm_mode.h               |   1 +
 21 files changed, 1265 insertions(+), 28 deletions(-)
 create mode 100644 drivers/gpu/drm/drm_writeback.c
 create mode 100644 drivers/gpu/drm/msm/disp/mdp5/mdp5_wb.c
 create mode 100644 include/drm/drm_writeback.h

-- 
2.14.3

_______________________________________________
Freedreno mailing list
Freedreno@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/freedreno

^ permalink raw reply	[flat|nested] 41+ messages in thread

* [RFC 1/4] drm: Add writeback connector type
  2018-02-23 13:17 [RFC 0/4] drm/msm/mdp5: writeback connector support Rob Clark
@ 2018-02-23 13:17     ` Rob Clark
  0 siblings, 0 replies; 41+ messages in thread
From: Rob Clark @ 2018-02-23 13:17 UTC (permalink / raw)
  To: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW
  Cc: Jonathan Corbet, Maarten Lankhorst,
	linux-arm-msm-u79uwXL29TY76Z2rM5mHXA,
	linux-doc-u79uwXL29TY76Z2rM5mHXA, Liviu Dudau,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA, David Airlie, Rob Clark,
	Sean Paul, Gustavo Padovan, Mihail Atanassov,
	freedreno-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, Brian Starkey

From: Brian Starkey <brian.starkey@arm.com>

Writeback connectors represent writeback engines which can write the
CRTC output to a memory framebuffer. Add a writeback connector type and
related support functions.

Drivers should initialize a writeback connector with
drm_writeback_connector_init() which takes care of setting up all the
writeback-specific details on top of the normal functionality of
drm_connector_init().

Writeback connectors have a WRITEBACK_FB_ID property, used to set the
output framebuffer, and a WRITEBACK_PIXEL_FORMATS blob used to expose the
supported writeback formats to userspace.

When a framebuffer is attached to a writeback connector with the
WRITEBACK_FB_ID property, it is used only once (for the commit in which
it was included), and userspace can never read back the value of
WRITEBACK_FB_ID. WRITEBACK_FB_ID can only be set if the connector is
attached to a CRTC.

Changes since v1:
 - Added drm_writeback.c + documentation
 - Added helper to initialize writeback connector in one go
 - Added core checks
 - Squashed into a single commit
 - Dropped the client cap
 - Writeback framebuffers are no longer persistent

Changes since v2:
 Daniel Vetter:
 - Subclass drm_connector to drm_writeback_connector
 - Relax check to allow CRTC to be set without an FB
 - Add some writeback_ prefixes
 - Drop PIXEL_FORMATS_SIZE property, as it was unnecessary
 Gustavo Padovan:
 - Add drm_writeback_job to handle writeback signalling centrally

Changes since v3:
 - Rebased
 - Rename PIXEL_FORMATS -> WRITEBACK_PIXEL_FORMATS

Changes since v4:
 - Added atomic_commit() vfunc to connector helper funcs, so that
   writeback jobs are committed from atomic helpers

Signed-off-by: Brian Starkey <brian.starkey@arm.com>
[rebased and fixed conflicts]
Signed-off-by: Mihail Atanassov <mihail.atanassov@arm.com>
Signed-off-by: Liviu Dudau <liviu.dudau@arm.com>
[rebased and added atomic_commit() vfunc for writeback jobs]
Signed-off-by: Rob Clark <robdclark@gmail.com>
---
 Documentation/gpu/drm-kms.rst            |   9 ++
 drivers/gpu/drm/Makefile                 |   2 +-
 drivers/gpu/drm/drm_atomic.c             | 130 ++++++++++++++++
 drivers/gpu/drm/drm_atomic_helper.c      |  30 ++++
 drivers/gpu/drm/drm_connector.c          |   4 +-
 drivers/gpu/drm/drm_writeback.c          | 257 +++++++++++++++++++++++++++++++
 include/drm/drm_atomic.h                 |   3 +
 include/drm/drm_connector.h              |  13 ++
 include/drm/drm_mode_config.h            |  14 ++
 include/drm/drm_modeset_helper_vtables.h |  11 ++
 include/drm/drm_writeback.h              |  89 +++++++++++
 include/uapi/drm/drm_mode.h              |   1 +
 12 files changed, 561 insertions(+), 2 deletions(-)
 create mode 100644 drivers/gpu/drm/drm_writeback.c
 create mode 100644 include/drm/drm_writeback.h

diff --git a/Documentation/gpu/drm-kms.rst b/Documentation/gpu/drm-kms.rst
index 2dcf5b42015d..e7590dd2f71e 100644
--- a/Documentation/gpu/drm-kms.rst
+++ b/Documentation/gpu/drm-kms.rst
@@ -370,6 +370,15 @@ Connector Functions Reference
 .. kernel-doc:: drivers/gpu/drm/drm_connector.c
    :export:
 
+Writeback Connectors
+--------------------
+
+.. kernel-doc:: drivers/gpu/drm/drm_writeback.c
+  :doc: overview
+
+.. kernel-doc:: drivers/gpu/drm/drm_writeback.c
+  :export:
+
 Encoder Abstraction
 ===================
 
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 50093ff4479b..3d708959b224 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -18,7 +18,7 @@ drm-y       :=	drm_auth.o drm_bufs.o drm_cache.o \
 		drm_encoder.o drm_mode_object.o drm_property.o \
 		drm_plane.o drm_color_mgmt.o drm_print.o \
 		drm_dumb_buffers.o drm_mode_config.o drm_vblank.o \
-		drm_syncobj.o drm_lease.o
+		drm_syncobj.o drm_lease.o drm_writeback.o
 
 drm-$(CONFIG_DRM_LIB_RANDOM) += lib/drm_random.o
 drm-$(CONFIG_DRM_VM) += drm_vm.o
diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
index 46733d534587..019f131fe8be 100644
--- a/drivers/gpu/drm/drm_atomic.c
+++ b/drivers/gpu/drm/drm_atomic.c
@@ -30,6 +30,7 @@
 #include <drm/drm_atomic.h>
 #include <drm/drm_mode.h>
 #include <drm/drm_print.h>
+#include <drm/drm_writeback.h>
 #include <linux/sync_file.h>
 
 #include "drm_crtc_internal.h"
@@ -638,6 +639,46 @@ static void drm_atomic_crtc_print_state(struct drm_printer *p,
 		crtc->funcs->atomic_print_state(p, state);
 }
 
+/**
+ * drm_atomic_connector_check - check connector state
+ * @connector: connector to check
+ * @state: connector state to check
+ *
+ * Provides core sanity checks for connector state.
+ *
+ * RETURNS:
+ * Zero on success, error code on failure
+ */
+static int drm_atomic_connector_check(struct drm_connector *connector,
+		struct drm_connector_state *state)
+{
+	struct drm_crtc_state *crtc_state;
+	struct drm_writeback_job *writeback_job = state->writeback_job;
+
+	if ((connector->connector_type != DRM_MODE_CONNECTOR_WRITEBACK) ||
+	    !writeback_job)
+		return 0;
+
+	if (writeback_job->fb && !state->crtc) {
+		DRM_DEBUG_ATOMIC("[CONNECTOR:%d:%s] framebuffer without CRTC\n",
+				 connector->base.id, connector->name);
+		return -EINVAL;
+	}
+
+	if (state->crtc)
+		crtc_state = drm_atomic_get_existing_crtc_state(state->state,
+								state->crtc);
+
+	if (writeback_job->fb && !crtc_state->active) {
+		DRM_DEBUG_ATOMIC("[CONNECTOR:%d:%s] has framebuffer, but [CRTC:%d] is off\n",
+				 connector->base.id, connector->name,
+				 state->crtc->base.id);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
 /**
  * drm_atomic_get_plane_state - get plane state
  * @state: global atomic state object
@@ -1230,6 +1271,12 @@ static int drm_atomic_connector_set_property(struct drm_connector *connector,
 			return -EINVAL;
 		}
 		state->content_protection = val;
+	} else if (property == config->writeback_fb_id_property) {
+		struct drm_framebuffer *fb = drm_framebuffer_lookup(dev, NULL, val);
+		int ret = drm_atomic_set_writeback_fb_for_connector(state, fb);
+		if (fb)
+			drm_framebuffer_unreference(fb);
+		return ret;
 	} else if (connector->funcs->atomic_set_property) {
 		return connector->funcs->atomic_set_property(connector,
 				state, property, val);
@@ -1311,6 +1358,9 @@ drm_atomic_connector_get_property(struct drm_connector *connector,
 		*val = state->scaling_mode;
 	} else if (property == connector->content_protection_property) {
 		*val = state->content_protection;
+	} else if (property == config->writeback_fb_id_property) {
+		/* Writeback framebuffer is one-shot, write and forget */
+		*val = 0;
 	} else if (connector->funcs->atomic_get_property) {
 		return connector->funcs->atomic_get_property(connector,
 				state, property, val);
@@ -1518,6 +1568,75 @@ drm_atomic_set_crtc_for_connector(struct drm_connector_state *conn_state,
 }
 EXPORT_SYMBOL(drm_atomic_set_crtc_for_connector);
 
+/*
+ * drm_atomic_get_writeback_job - return or allocate a writeback job
+ * @conn_state: Connector state to get the job for
+ *
+ * Writeback jobs have a different lifetime to the atomic state they are
+ * associated with. This convenience function takes care of allocating a job
+ * if there isn't yet one associated with the connector state, otherwise
+ * it just returns the existing job.
+ *
+ * Returns: The writeback job for the given connector state
+ */
+static struct drm_writeback_job *
+drm_atomic_get_writeback_job(struct drm_connector_state *conn_state)
+{
+	WARN_ON(conn_state->connector->connector_type !=
+		DRM_MODE_CONNECTOR_WRITEBACK);
+
+	if (!conn_state->writeback_job)
+		conn_state->writeback_job =
+			kzalloc(sizeof(*conn_state->writeback_job), GFP_KERNEL);
+
+	return conn_state->writeback_job;
+}
+
+/**
+ * drm_atomic_set_writeback_fb_for_connector - set writeback framebuffer
+ * @conn_state: atomic state object for the connector
+ * @fb: fb to use for the connector
+ *
+ * This is used to set the framebuffer for a writeback connector, which outputs
+ * to a buffer instead of an actual physical connector.
+ * Changing the assigned framebuffer requires us to grab a reference to the new
+ * fb and drop the reference to the old fb, if there is one. This function
+ * takes care of all these details besides updating the pointer in the
+ * state object itself.
+ *
+ * Note: The only way conn_state can already have an fb set is if the commit
+ * sets the property more than once.
+ *
+ * See also: drm_writeback_connector_init()
+ *
+ * Returns: 0 on success
+ */
+int drm_atomic_set_writeback_fb_for_connector(
+		struct drm_connector_state *conn_state,
+		struct drm_framebuffer *fb)
+{
+	struct drm_writeback_job *job =
+		drm_atomic_get_writeback_job(conn_state);
+	if (!job)
+		return -ENOMEM;
+
+	if (job->fb)
+		drm_framebuffer_unreference(job->fb);
+	if (fb)
+		drm_framebuffer_reference(fb);
+	job->fb = fb;
+
+	if (fb)
+		DRM_DEBUG_ATOMIC("Set [FB:%d] for connector state %p\n",
+				 fb->base.id, conn_state);
+	else
+		DRM_DEBUG_ATOMIC("Set [NOFB] for connector state %p\n",
+				 conn_state);
+
+	return 0;
+}
+EXPORT_SYMBOL(drm_atomic_set_writeback_fb_for_connector);
+
 /**
  * drm_atomic_add_affected_connectors - add connectors for crtc
  * @state: atomic state
@@ -1636,6 +1755,8 @@ int drm_atomic_check_only(struct drm_atomic_state *state)
 	struct drm_plane_state *plane_state;
 	struct drm_crtc *crtc;
 	struct drm_crtc_state *crtc_state;
+	struct drm_connector *conn;
+	struct drm_connector_state *conn_state;
 	int i, ret = 0;
 
 	DRM_DEBUG_ATOMIC("checking %p\n", state);
@@ -1658,6 +1779,15 @@ int drm_atomic_check_only(struct drm_atomic_state *state)
 		}
 	}
 
+	for_each_new_connector_in_state(state, conn, conn_state, i) {
+		ret = drm_atomic_connector_check(conn, conn_state);
+		if (ret) {
+			DRM_DEBUG_ATOMIC("[CONNECTOR:%d:%s] atomic core check failed\n",
+					 conn->base.id, conn->name);
+			return ret;
+		}
+	}
+
 	if (config->funcs->atomic_check)
 		ret = config->funcs->atomic_check(state->dev, state);
 
diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c
index ae3cbfe9e01c..12b910755d84 100644
--- a/drivers/gpu/drm/drm_atomic_helper.c
+++ b/drivers/gpu/drm/drm_atomic_helper.c
@@ -30,6 +30,7 @@
 #include <drm/drm_plane_helper.h>
 #include <drm/drm_crtc_helper.h>
 #include <drm/drm_atomic_helper.h>
+#include <drm/drm_writeback.h>
 #include <linux/dma-fence.h>
 
 #include "drm_crtc_helper_internal.h"
@@ -1159,6 +1160,27 @@ void drm_atomic_helper_commit_modeset_disables(struct drm_device *dev,
 }
 EXPORT_SYMBOL(drm_atomic_helper_commit_modeset_disables);
 
+static void commit_writebacks(struct drm_device *dev, struct drm_atomic_state *old_state)
+{
+	struct drm_connector *connector;
+	struct drm_connector_state *new_conn_state;
+	int i;
+
+	for_each_new_connector_in_state(old_state, connector, new_conn_state, i) {
+		const struct drm_connector_helper_funcs *funcs;
+
+		funcs = connector->helper_private;
+
+		if (new_conn_state->writeback_job &&
+		    new_conn_state->writeback_job->fb) {
+			WARN_ON(connector->connector_type !=
+				DRM_MODE_CONNECTOR_WRITEBACK);
+			funcs->atomic_commit(connector,
+					     new_conn_state->writeback_job);
+		}
+	}
+}
+
 /**
  * drm_atomic_helper_commit_modeset_enables - modeset commit to enable outputs
  * @dev: DRM device
@@ -1238,6 +1260,8 @@ void drm_atomic_helper_commit_modeset_enables(struct drm_device *dev,
 
 		drm_bridge_enable(encoder->bridge);
 	}
+
+	commit_writebacks(dev, old_state);
 }
 EXPORT_SYMBOL(drm_atomic_helper_commit_modeset_enables);
 
@@ -3627,6 +3651,9 @@ __drm_atomic_helper_connector_duplicate_state(struct drm_connector *connector,
 	if (state->crtc)
 		drm_connector_get(connector);
 	state->commit = NULL;
+
+	/* Don't copy over a writeback job, they are used only once */
+	state->writeback_job = NULL;
 }
 EXPORT_SYMBOL(__drm_atomic_helper_connector_duplicate_state);
 
@@ -3756,6 +3783,9 @@ __drm_atomic_helper_connector_destroy_state(struct drm_connector_state *state)
 
 	if (state->commit)
 		drm_crtc_commit_put(state->commit);
+
+	if (state->writeback_job)
+		drm_writeback_cleanup_job(state->writeback_job);
 }
 EXPORT_SYMBOL(__drm_atomic_helper_connector_destroy_state);
 
diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c
index 16b9c3810af2..add47f06ae70 100644
--- a/drivers/gpu/drm/drm_connector.c
+++ b/drivers/gpu/drm/drm_connector.c
@@ -87,6 +87,7 @@ static struct drm_conn_prop_enum_list drm_connector_enum_list[] = {
 	{ DRM_MODE_CONNECTOR_VIRTUAL, "Virtual" },
 	{ DRM_MODE_CONNECTOR_DSI, "DSI" },
 	{ DRM_MODE_CONNECTOR_DPI, "DPI" },
+	{ DRM_MODE_CONNECTOR_WRITEBACK, "Writeback" },
 };
 
 void drm_connector_ida_init(void)
@@ -249,7 +250,8 @@ int drm_connector_init(struct drm_device *dev,
 	config->num_connector++;
 	spin_unlock_irq(&config->connector_list_lock);
 
-	if (connector_type != DRM_MODE_CONNECTOR_VIRTUAL)
+	if (connector_type != DRM_MODE_CONNECTOR_VIRTUAL &&
+	    connector_type != DRM_MODE_CONNECTOR_WRITEBACK)
 		drm_object_attach_property(&connector->base,
 					      config->edid_property,
 					      0);
diff --git a/drivers/gpu/drm/drm_writeback.c b/drivers/gpu/drm/drm_writeback.c
new file mode 100644
index 000000000000..da61f929cbc3
--- /dev/null
+++ b/drivers/gpu/drm/drm_writeback.c
@@ -0,0 +1,257 @@
+/*
+ * (C) COPYRIGHT 2016 ARM Limited. All rights reserved.
+ * Author: Brian Starkey <brian.starkey@arm.com>
+ *
+ * This program is free software and is provided to you under the terms of the
+ * GNU General Public License version 2 as published by the Free Software
+ * Foundation, and any use by you of this program is subject to the terms
+ * of such GNU licence.
+ */
+
+#include <drm/drm_crtc.h>
+#include <drm/drm_modeset_helper_vtables.h>
+#include <drm/drm_property.h>
+#include <drm/drm_writeback.h>
+#include <drm/drmP.h>
+
+/**
+ * DOC: overview
+ *
+ * Writeback connectors are used to expose hardware which can write the output
+ * from a CRTC to a memory buffer. They are used and act similarly to other
+ * types of connectors, with some important differences:
+ *  - Writeback connectors don't provide a way to output visually to the user.
+ *  - Writeback connectors should always report as "disconnected" (so that
+ *    clients which don't understand them will ignore them).
+ *  - Writeback connectors don't have EDID.
+ *
+ * A framebuffer may only be attached to a writeback connector when the
+ * connector is attached to a CRTC. The WRITEBACK_FB_ID property which sets the
+ * framebuffer applies only to a single commit (see below). A framebuffer may
+ * not be attached while the CRTC is off.
+ *
+ * Writeback connectors have some additional properties, which userspace
+ * can use to query and control them:
+ *
+ *  "WRITEBACK_FB_ID":
+ *	Write-only object property storing a DRM_MODE_OBJECT_FB: it stores the
+ *	framebuffer to be written by the writeback connector. This property is
+ *	similar to the FB_ID property on planes, but will always read as zero
+ *	and is not preserved across commits.
+ *	Userspace must set this property to an output buffer every time it
+ *	wishes the buffer to get filled.
+ *
+ *  "WRITEBACK_PIXEL_FORMATS":
+ *	Immutable blob property to store the supported pixel formats table. The
+ *	data is an array of u32 DRM_FORMAT_* fourcc values.
+ *	Userspace can use this blob to find out what pixel formats are supported
+ *	by the connector's writeback engine.
+ */
+
+static bool create_writeback_properties(struct drm_device *dev)
+{
+	struct drm_property *prop;
+
+	if (!dev->mode_config.writeback_fb_id_property) {
+		prop = drm_property_create_object(dev, DRM_MODE_PROP_ATOMIC,
+						  "WRITEBACK_FB_ID",
+						  DRM_MODE_OBJECT_FB);
+		if (!prop)
+			return false;
+		dev->mode_config.writeback_fb_id_property = prop;
+	}
+
+	if (!dev->mode_config.writeback_pixel_formats_property) {
+		prop = drm_property_create(dev, DRM_MODE_PROP_BLOB | DRM_MODE_PROP_IMMUTABLE,
+					   "WRITEBACK_PIXEL_FORMATS", 0);
+		if (!prop)
+			return false;
+		dev->mode_config.writeback_pixel_formats_property = prop;
+	}
+
+	return true;
+}
+
+static const struct drm_encoder_funcs drm_writeback_encoder_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
+/**
+ * drm_writeback_connector_init - Initialize a writeback connector and its properties
+ * @dev: DRM device
+ * @wb_connector: Writeback connector to initialize
+ * @con_funcs: Connector funcs vtable
+ * @enc_helper_funcs: Encoder helper funcs vtable to be used by the internal encoder
+ * @formats: Array of supported pixel formats for the writeback engine
+ * @n_formats: Length of the formats array
+ *
+ * This function creates the writeback-connector-specific properties if they
+ * have not been already created, initializes the connector as
+ * type DRM_MODE_CONNECTOR_WRITEBACK, and correctly initializes the property
+ * values. It will also create an internal encoder associated with the
+ * drm_writeback_connector and set it to use the @enc_helper_funcs vtable for
+ * the encoder helper.
+ *
+ * Drivers should always use this function instead of drm_connector_init() to
+ * set up writeback connectors.
+ *
+ * Returns: 0 on success, or a negative error code
+ */
+int drm_writeback_connector_init(struct drm_device *dev,
+				 struct drm_writeback_connector *wb_connector,
+				 const struct drm_connector_funcs *con_funcs,
+				 const struct drm_encoder_helper_funcs *enc_helper_funcs,
+				 const u32 *formats, int n_formats)
+{
+	int ret;
+	struct drm_property_blob *blob;
+	struct drm_connector *connector = &wb_connector->base;
+	struct drm_mode_config *config = &dev->mode_config;
+
+	if (!create_writeback_properties(dev))
+		return -EINVAL;
+
+	blob = drm_property_create_blob(dev, n_formats * sizeof(*formats),
+					formats);
+	if (IS_ERR(blob))
+		return PTR_ERR(blob);
+
+	drm_encoder_helper_add(&wb_connector->encoder, enc_helper_funcs);
+	ret = drm_encoder_init(dev, &wb_connector->encoder,
+			       &drm_writeback_encoder_funcs,
+			       DRM_MODE_ENCODER_VIRTUAL, NULL);
+	if (ret)
+		goto fail;
+
+	connector->interlace_allowed = 0;
+
+	ret = drm_connector_init(dev, connector, con_funcs,
+				 DRM_MODE_CONNECTOR_WRITEBACK);
+	if (ret)
+		goto connector_fail;
+
+	ret = drm_mode_connector_attach_encoder(connector,
+						&wb_connector->encoder);
+	if (ret)
+		goto attach_fail;
+
+	INIT_LIST_HEAD(&wb_connector->job_queue);
+	spin_lock_init(&wb_connector->job_lock);
+
+	drm_object_attach_property(&connector->base,
+				   config->writeback_fb_id_property, 0);
+
+	drm_object_attach_property(&connector->base,
+				   config->writeback_pixel_formats_property,
+				   blob->base.id);
+	wb_connector->pixel_formats_blob_ptr = blob;
+
+	return 0;
+
+attach_fail:
+	drm_connector_cleanup(connector);
+connector_fail:
+	drm_encoder_cleanup(&wb_connector->encoder);
+fail:
+	drm_property_unreference_blob(blob);
+	return ret;
+}
+EXPORT_SYMBOL(drm_writeback_connector_init);
+
+/**
+ * drm_writeback_queue_job - Queue a writeback job for later signalling
+ * @wb_connector: The writeback connector to queue a job on
+ * @job: The job to queue
+ *
+ * This function adds a job to the job_queue for a writeback connector. It
+ * should be considered to take ownership of the writeback job, and so any other
+ * references to the job must be cleared after calling this function.
+ *
+ * Drivers must ensure that for a given writeback connector, jobs are queued in
+ * exactly the same order as they will be completed by the hardware (and
+ * signaled via drm_writeback_signal_completion).
+ *
+ * For every call to drm_writeback_queue_job() there must be exactly one call to
+ * drm_writeback_signal_completion()
+ *
+ * See also: drm_writeback_signal_completion()
+ */
+void drm_writeback_queue_job(struct drm_writeback_connector *wb_connector,
+			     struct drm_writeback_job *job)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&wb_connector->job_lock, flags);
+	list_add_tail(&job->list_entry, &wb_connector->job_queue);
+	spin_unlock_irqrestore(&wb_connector->job_lock, flags);
+}
+EXPORT_SYMBOL(drm_writeback_queue_job);
+
+/**
+ * drm_writeback_cleanup_job - Cleanup and free a writeback job
+ * @job: The writeback job to free
+ *
+ * Drops any references held by the writeback job, and frees the structure.
+ */
+void drm_writeback_cleanup_job(struct drm_writeback_job *job)
+{
+	if (!job)
+		return;
+
+	if (job->fb)
+		drm_framebuffer_unreference(job->fb);
+	kfree(job);
+}
+EXPORT_SYMBOL(drm_writeback_cleanup_job);
+
+/*
+ * @cleanup_work: deferred cleanup of a writeback job
+ *
+ * The job cannot be cleaned up directly in drm_writeback_signal_completion,
+ * because it may be called in interrupt context. Dropping the framebuffer
+ * reference can sleep, and so the cleanup is deferred to a workqueue.
+ */
+static void cleanup_work(struct work_struct *work)
+{
+	struct drm_writeback_job *job = container_of(work,
+						     struct drm_writeback_job,
+						     cleanup_work);
+	drm_writeback_cleanup_job(job);
+}
+
+/**
+ * drm_writeback_signal_completion - Signal the completion of a writeback job
+ * @wb_connector: The writeback connector whose job is complete
+ *
+ * Drivers should call this to signal the completion of a previously queued
+ * writeback job. It should be called as soon as possible after the hardware
+ * has finished writing, and may be called from interrupt context.
+ * It is the driver's responsibility to ensure that for a given connector, the
+ * hardware completes writeback jobs in the same order as they are queued.
+ *
+ * Unless the driver is holding its own reference to the framebuffer, it must
+ * not be accessed after calling this function.
+ *
+ * See also: drm_writeback_queue_job()
+ */
+void
+drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector)
+{
+	unsigned long flags;
+	struct drm_writeback_job *job;
+
+	spin_lock_irqsave(&wb_connector->job_lock, flags);
+	job = list_first_entry_or_null(&wb_connector->job_queue,
+				       struct drm_writeback_job,
+				       list_entry);
+	if (job)
+		list_del(&job->list_entry);
+	spin_unlock_irqrestore(&wb_connector->job_lock, flags);
+
+	if (WARN_ON(!job))
+		return;
+
+	INIT_WORK(&job->cleanup_work, cleanup_work);
+	queue_work(system_long_wq, &job->cleanup_work);
+}
+EXPORT_SYMBOL(drm_writeback_signal_completion);
diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
index cf13842a6dbd..d7b0263cc5cf 100644
--- a/include/drm/drm_atomic.h
+++ b/include/drm/drm_atomic.h
@@ -594,6 +594,9 @@ void drm_atomic_set_fence_for_plane(struct drm_plane_state *plane_state,
 int __must_check
 drm_atomic_set_crtc_for_connector(struct drm_connector_state *conn_state,
 				  struct drm_crtc *crtc);
+int drm_atomic_set_writeback_fb_for_connector(
+		struct drm_connector_state *conn_state,
+		struct drm_framebuffer *fb);
 int __must_check
 drm_atomic_add_affected_connectors(struct drm_atomic_state *state,
 				   struct drm_crtc *crtc);
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
index 758a176e7b57..8701ebcc68b3 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -425,6 +425,19 @@ struct drm_connector_state {
 	 * protection. This is most commonly used for HDCP.
 	 */
 	unsigned int content_protection;
+
+	/**
+	 * @writeback_job: Writeback job for writeback connectors
+	 *
+	 * Holds the framebuffer for a writeback connector. As the writeback
+	 * completion may be asynchronous to the normal commit cycle, the
+	 * writeback job lifetime is managed separately from the normal atomic
+	 * state by this object.
+	 *
+	 * See also: drm_writeback_queue_job() and
+	 * drm_writeback_signal_completion()
+	 */
+	struct drm_writeback_job *writeback_job;
 };
 
 /**
diff --git a/include/drm/drm_mode_config.h b/include/drm/drm_mode_config.h
index 7569f22ffef6..c012e1148ec0 100644
--- a/include/drm/drm_mode_config.h
+++ b/include/drm/drm_mode_config.h
@@ -779,6 +779,20 @@ struct drm_mode_config {
 	 */
 	struct drm_property *panel_orientation_property;
 
+	/**
+	 * @writeback_fb_id_property: Property for writeback connectors, storing
+	 * the ID of the output framebuffer.
+	 * See also: drm_writeback_connector_init()
+	 */
+	struct drm_property *writeback_fb_id_property;
+	/**
+	 * @writeback_pixel_formats_property: Property for writeback connectors,
+	 * storing an array of the supported pixel formats for the writeback
+	 * engine (read-only).
+	 * See also: drm_writeback_connector_init()
+	 */
+	struct drm_property *writeback_pixel_formats_property;
+
 	/* dumb ioctl parameters */
 	uint32_t preferred_depth, prefer_shadow;
 
diff --git a/include/drm/drm_modeset_helper_vtables.h b/include/drm/drm_modeset_helper_vtables.h
index 3e76ca805b0f..97d3a810bc85 100644
--- a/include/drm/drm_modeset_helper_vtables.h
+++ b/include/drm/drm_modeset_helper_vtables.h
@@ -974,6 +974,17 @@ struct drm_connector_helper_funcs {
 	 */
 	int (*atomic_check)(struct drm_connector *connector,
 			    struct drm_connector_state *state);
+
+	/**
+	 * @atomic_commit:
+	 *
+	 * For write-back connectors, this is the point to commit the write-
+	 * back job to hw.
+	 *
+	 * This callback is used by the atomic modeset helpers.
+	 */
+	void (*atomic_commit)(struct drm_connector *connector,
+			      struct drm_writeback_job *writeback_job);
 };
 
 /**
diff --git a/include/drm/drm_writeback.h b/include/drm/drm_writeback.h
new file mode 100644
index 000000000000..0bb95fd4907d
--- /dev/null
+++ b/include/drm/drm_writeback.h
@@ -0,0 +1,89 @@
+/*
+ * (C) COPYRIGHT 2016 ARM Limited. All rights reserved.
+ * Author: Brian Starkey <brian.starkey@arm.com>
+ *
+ * This program is free software and is provided to you under the terms of the
+ * GNU General Public License version 2 as published by the Free Software
+ * Foundation, and any use by you of this program is subject to the terms
+ * of such GNU licence.
+ */
+
+#ifndef __DRM_WRITEBACK_H__
+#define __DRM_WRITEBACK_H__
+#include <drm/drm_connector.h>
+#include <drm/drm_encoder.h>
+#include <linux/workqueue.h>
+
+struct drm_writeback_connector {
+	struct drm_connector base;
+
+	/**
+	 * @encoder: Internal encoder used by the connector to fulfill
+	 * the DRM framework requirements. The users of the
+	 * @drm_writeback_connector control the behaviour of the @encoder
+	 * by passing the @enc_funcs parameter to drm_writeback_connector_init()
+	 * function.
+	 */
+	struct drm_encoder encoder;
+
+	/**
+	 * @pixel_formats_blob_ptr:
+	 *
+	 * DRM blob property data for the pixel formats list on writeback
+	 * connectors
+	 * See also drm_writeback_connector_init()
+	 */
+	struct drm_property_blob *pixel_formats_blob_ptr;
+
+	/** @job_lock: Protects job_queue */
+	spinlock_t job_lock;
+
+	/**
+	 * @job_queue:
+	 *
+	 * Holds a list of a connector's writeback jobs; the last item is the
+	 * most recent. The first item may be either waiting for the hardware
+	 * to begin writing, or currently being written.
+	 *
+	 * See also: drm_writeback_queue_job() and
+	 * drm_writeback_signal_completion()
+	 */
+	struct list_head job_queue;
+};
+#define to_wb_connector(x) container_of(x, struct drm_writeback_connector, base)
+
+struct drm_writeback_job {
+	/**
+	 * @cleanup_work:
+	 *
+	 * Used to allow drm_writeback_signal_completion to defer dropping the
+	 * framebuffer reference to a workqueue.
+	 */
+	struct work_struct cleanup_work;
+	/**
+	 * @list_entry:
+	 *
+	 * List item for the connector's @job_queue
+	 */
+	struct list_head list_entry;
+	/**
+	 * @fb:
+	 *
+	 * Framebuffer to be written to by the writeback connector. Do not set
+	 * directly, use drm_atomic_set_writeback_fb_for_connector()
+	 */
+	struct drm_framebuffer *fb;
+};
+
+int drm_writeback_connector_init(struct drm_device *dev,
+				 struct drm_writeback_connector *wb_connector,
+				 const struct drm_connector_funcs *con_funcs,
+				 const struct drm_encoder_helper_funcs *enc_helper_funcs,
+				 const u32 *formats, int n_formats);
+
+void drm_writeback_queue_job(struct drm_writeback_connector *wb_connector,
+			     struct drm_writeback_job *job);
+
+void drm_writeback_cleanup_job(struct drm_writeback_job *job);
+void drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector);
+#endif
diff --git a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h
index 2c575794fb52..7b47e184e95e 100644
--- a/include/uapi/drm/drm_mode.h
+++ b/include/uapi/drm/drm_mode.h
@@ -338,6 +338,7 @@ enum drm_mode_subconnector {
 #define DRM_MODE_CONNECTOR_VIRTUAL      15
 #define DRM_MODE_CONNECTOR_DSI		16
 #define DRM_MODE_CONNECTOR_DPI		17
+#define DRM_MODE_CONNECTOR_WRITEBACK	18
 
 struct drm_mode_get_connector {
 
-- 
2.14.3

_______________________________________________
Freedreno mailing list
Freedreno@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/freedreno

^ permalink raw reply related	[flat|nested] 41+ messages in thread

* [RFC 1/4] drm: Add writeback connector type
@ 2018-02-23 13:17     ` Rob Clark
  0 siblings, 0 replies; 41+ messages in thread
From: Rob Clark @ 2018-02-23 13:17 UTC (permalink / raw)
  To: dri-devel
  Cc: freedreno, linux-arm-msm, Brian Starkey, Liviu Dudau,
	Mihail Atanassov, Rob Clark, Gustavo Padovan, Maarten Lankhorst,
	Sean Paul, David Airlie, Jonathan Corbet, linux-doc,
	linux-kernel

From: Brian Starkey <brian.starkey@arm.com>

Writeback connectors represent writeback engines which can write the
CRTC output to a memory framebuffer. Add a writeback connector type and
related support functions.

Drivers should initialize a writeback connector with
drm_writeback_connector_init() which takes care of setting up all the
writeback-specific details on top of the normal functionality of
drm_connector_init().

Writeback connectors have a WRITEBACK_FB_ID property, used to set the
output framebuffer, and a WRITEBACK_PIXEL_FORMATS blob used to expose the
supported writeback formats to userspace.

When a framebuffer is attached to a writeback connector with the
WRITEBACK_FB_ID property, it is used only once (for the commit in which
it was included), and userspace can never read back the value of
WRITEBACK_FB_ID. WRITEBACK_FB_ID can only be set if the connector is
attached to a CRTC.

Changes since v1:
 - Added drm_writeback.c + documentation
 - Added helper to initialize writeback connector in one go
 - Added core checks
 - Squashed into a single commit
 - Dropped the client cap
 - Writeback framebuffers are no longer persistent

Changes since v2:
 Daniel Vetter:
 - Subclass drm_connector to drm_writeback_connector
 - Relax check to allow CRTC to be set without an FB
 - Add some writeback_ prefixes
 - Drop PIXEL_FORMATS_SIZE property, as it was unnecessary
 Gustavo Padovan:
 - Add drm_writeback_job to handle writeback signalling centrally

Changes since v3:
 - Rebased
 - Rename PIXEL_FORMATS -> WRITEBACK_PIXEL_FORMATS

Changes since v4:
 - Added atomic_commit() vfunc to connector helper funcs, so that
   writeback jobs are committed from atomic helpers

Signed-off-by: Brian Starkey <brian.starkey@arm.com>
[rebased and fixed conflicts]
Signed-off-by: Mihail Atanassov <mihail.atanassov@arm.com>
Signed-off-by: Liviu Dudau <liviu.dudau@arm.com>
[rebased and added atomic_commit() vfunc for writeback jobs]
Signed-off-by: Rob Clark <robdclark@gmail.com>
---
 Documentation/gpu/drm-kms.rst            |   9 ++
 drivers/gpu/drm/Makefile                 |   2 +-
 drivers/gpu/drm/drm_atomic.c             | 130 ++++++++++++++++
 drivers/gpu/drm/drm_atomic_helper.c      |  30 ++++
 drivers/gpu/drm/drm_connector.c          |   4 +-
 drivers/gpu/drm/drm_writeback.c          | 257 +++++++++++++++++++++++++++++++
 include/drm/drm_atomic.h                 |   3 +
 include/drm/drm_connector.h              |  13 ++
 include/drm/drm_mode_config.h            |  14 ++
 include/drm/drm_modeset_helper_vtables.h |  11 ++
 include/drm/drm_writeback.h              |  89 +++++++++++
 include/uapi/drm/drm_mode.h              |   1 +
 12 files changed, 561 insertions(+), 2 deletions(-)
 create mode 100644 drivers/gpu/drm/drm_writeback.c
 create mode 100644 include/drm/drm_writeback.h

diff --git a/Documentation/gpu/drm-kms.rst b/Documentation/gpu/drm-kms.rst
index 2dcf5b42015d..e7590dd2f71e 100644
--- a/Documentation/gpu/drm-kms.rst
+++ b/Documentation/gpu/drm-kms.rst
@@ -370,6 +370,15 @@ Connector Functions Reference
 .. kernel-doc:: drivers/gpu/drm/drm_connector.c
    :export:
 
+Writeback Connectors
+--------------------
+
+.. kernel-doc:: drivers/gpu/drm/drm_writeback.c
+  :doc: overview
+
+.. kernel-doc:: drivers/gpu/drm/drm_writeback.c
+  :export:
+
 Encoder Abstraction
 ===================
 
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 50093ff4479b..3d708959b224 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -18,7 +18,7 @@ drm-y       :=	drm_auth.o drm_bufs.o drm_cache.o \
 		drm_encoder.o drm_mode_object.o drm_property.o \
 		drm_plane.o drm_color_mgmt.o drm_print.o \
 		drm_dumb_buffers.o drm_mode_config.o drm_vblank.o \
-		drm_syncobj.o drm_lease.o
+		drm_syncobj.o drm_lease.o drm_writeback.o
 
 drm-$(CONFIG_DRM_LIB_RANDOM) += lib/drm_random.o
 drm-$(CONFIG_DRM_VM) += drm_vm.o
diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
index 46733d534587..019f131fe8be 100644
--- a/drivers/gpu/drm/drm_atomic.c
+++ b/drivers/gpu/drm/drm_atomic.c
@@ -30,6 +30,7 @@
 #include <drm/drm_atomic.h>
 #include <drm/drm_mode.h>
 #include <drm/drm_print.h>
+#include <drm/drm_writeback.h>
 #include <linux/sync_file.h>
 
 #include "drm_crtc_internal.h"
@@ -638,6 +639,46 @@ static void drm_atomic_crtc_print_state(struct drm_printer *p,
 		crtc->funcs->atomic_print_state(p, state);
 }
 
+/**
+ * drm_atomic_connector_check - check connector state
+ * @connector: connector to check
+ * @state: connector state to check
+ *
+ * Provides core sanity checks for connector state.
+ *
+ * RETURNS:
+ * Zero on success, error code on failure
+ */
+static int drm_atomic_connector_check(struct drm_connector *connector,
+		struct drm_connector_state *state)
+{
+	struct drm_crtc_state *crtc_state;
+	struct drm_writeback_job *writeback_job = state->writeback_job;
+
+	if ((connector->connector_type != DRM_MODE_CONNECTOR_WRITEBACK) ||
+	    !writeback_job)
+		return 0;
+
+	if (writeback_job->fb && !state->crtc) {
+		DRM_DEBUG_ATOMIC("[CONNECTOR:%d:%s] framebuffer without CRTC\n",
+				 connector->base.id, connector->name);
+		return -EINVAL;
+	}
+
+	if (state->crtc)
+		crtc_state = drm_atomic_get_existing_crtc_state(state->state,
+								state->crtc);
+
+	if (writeback_job->fb && !crtc_state->active) {
+		DRM_DEBUG_ATOMIC("[CONNECTOR:%d:%s] has framebuffer, but [CRTC:%d] is off\n",
+				 connector->base.id, connector->name,
+				 state->crtc->base.id);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
 /**
  * drm_atomic_get_plane_state - get plane state
  * @state: global atomic state object
@@ -1230,6 +1271,12 @@ static int drm_atomic_connector_set_property(struct drm_connector *connector,
 			return -EINVAL;
 		}
 		state->content_protection = val;
+	} else if (property == config->writeback_fb_id_property) {
+		struct drm_framebuffer *fb = drm_framebuffer_lookup(dev, NULL, val);
+		int ret = drm_atomic_set_writeback_fb_for_connector(state, fb);
+		if (fb)
+			drm_framebuffer_unreference(fb);
+		return ret;
 	} else if (connector->funcs->atomic_set_property) {
 		return connector->funcs->atomic_set_property(connector,
 				state, property, val);
@@ -1311,6 +1358,9 @@ drm_atomic_connector_get_property(struct drm_connector *connector,
 		*val = state->scaling_mode;
 	} else if (property == connector->content_protection_property) {
 		*val = state->content_protection;
+	} else if (property == config->writeback_fb_id_property) {
+		/* Writeback framebuffer is one-shot, write and forget */
+		*val = 0;
 	} else if (connector->funcs->atomic_get_property) {
 		return connector->funcs->atomic_get_property(connector,
 				state, property, val);
@@ -1518,6 +1568,75 @@ drm_atomic_set_crtc_for_connector(struct drm_connector_state *conn_state,
 }
 EXPORT_SYMBOL(drm_atomic_set_crtc_for_connector);
 
+/*
+ * drm_atomic_get_writeback_job - return or allocate a writeback job
+ * @conn_state: Connector state to get the job for
+ *
+ * Writeback jobs have a different lifetime to the atomic state they are
+ * associated with. This convenience function takes care of allocating a job
+ * if there isn't yet one associated with the connector state, otherwise
+ * it just returns the existing job.
+ *
+ * Returns: The writeback job for the given connector state
+ */
+static struct drm_writeback_job *
+drm_atomic_get_writeback_job(struct drm_connector_state *conn_state)
+{
+	WARN_ON(conn_state->connector->connector_type !=
+		DRM_MODE_CONNECTOR_WRITEBACK);
+
+	if (!conn_state->writeback_job)
+		conn_state->writeback_job =
+			kzalloc(sizeof(*conn_state->writeback_job), GFP_KERNEL);
+
+	return conn_state->writeback_job;
+}
+
+/**
+ * drm_atomic_set_writeback_fb_for_connector - set writeback framebuffer
+ * @conn_state: atomic state object for the connector
+ * @fb: fb to use for the connector
+ *
+ * This is used to set the framebuffer for a writeback connector, which outputs
+ * to a buffer instead of an actual physical connector.
+ * Changing the assigned framebuffer requires us to grab a reference to the new
+ * fb and drop the reference to the old fb, if there is one. This function
+ * takes care of all these details besides updating the pointer in the
+ * state object itself.
+ *
+ * Note: The only way conn_state can already have an fb set is if the commit
+ * sets the property more than once.
+ *
+ * See also: drm_writeback_connector_init()
+ *
+ * Returns: 0 on success
+ */
+int drm_atomic_set_writeback_fb_for_connector(
+		struct drm_connector_state *conn_state,
+		struct drm_framebuffer *fb)
+{
+	struct drm_writeback_job *job =
+		drm_atomic_get_writeback_job(conn_state);
+	if (!job)
+		return -ENOMEM;
+
+	if (job->fb)
+		drm_framebuffer_unreference(job->fb);
+	if (fb)
+		drm_framebuffer_reference(fb);
+	job->fb = fb;
+
+	if (fb)
+		DRM_DEBUG_ATOMIC("Set [FB:%d] for connector state %p\n",
+				 fb->base.id, conn_state);
+	else
+		DRM_DEBUG_ATOMIC("Set [NOFB] for connector state %p\n",
+				 conn_state);
+
+	return 0;
+}
+EXPORT_SYMBOL(drm_atomic_set_writeback_fb_for_connector);
+
 /**
  * drm_atomic_add_affected_connectors - add connectors for crtc
  * @state: atomic state
@@ -1636,6 +1755,8 @@ int drm_atomic_check_only(struct drm_atomic_state *state)
 	struct drm_plane_state *plane_state;
 	struct drm_crtc *crtc;
 	struct drm_crtc_state *crtc_state;
+	struct drm_connector *conn;
+	struct drm_connector_state *conn_state;
 	int i, ret = 0;
 
 	DRM_DEBUG_ATOMIC("checking %p\n", state);
@@ -1658,6 +1779,15 @@ int drm_atomic_check_only(struct drm_atomic_state *state)
 		}
 	}
 
+	for_each_new_connector_in_state(state, conn, conn_state, i) {
+		ret = drm_atomic_connector_check(conn, conn_state);
+		if (ret) {
+			DRM_DEBUG_ATOMIC("[CONNECTOR:%d:%s] atomic core check failed\n",
+					 conn->base.id, conn->name);
+			return ret;
+		}
+	}
+
 	if (config->funcs->atomic_check)
 		ret = config->funcs->atomic_check(state->dev, state);
 
diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c
index ae3cbfe9e01c..12b910755d84 100644
--- a/drivers/gpu/drm/drm_atomic_helper.c
+++ b/drivers/gpu/drm/drm_atomic_helper.c
@@ -30,6 +30,7 @@
 #include <drm/drm_plane_helper.h>
 #include <drm/drm_crtc_helper.h>
 #include <drm/drm_atomic_helper.h>
+#include <drm/drm_writeback.h>
 #include <linux/dma-fence.h>
 
 #include "drm_crtc_helper_internal.h"
@@ -1159,6 +1160,27 @@ void drm_atomic_helper_commit_modeset_disables(struct drm_device *dev,
 }
 EXPORT_SYMBOL(drm_atomic_helper_commit_modeset_disables);
 
+static void commit_writebacks(struct drm_device *dev, struct drm_atomic_state *old_state)
+{
+	struct drm_connector *connector;
+	struct drm_connector_state *new_conn_state;
+	int i;
+
+	for_each_new_connector_in_state(old_state, connector, new_conn_state, i) {
+		const struct drm_connector_helper_funcs *funcs;
+
+		funcs = connector->helper_private;
+
+		if (new_conn_state->writeback_job &&
+		    new_conn_state->writeback_job->fb) {
+			WARN_ON(connector->connector_type !=
+				DRM_MODE_CONNECTOR_WRITEBACK);
+			funcs->atomic_commit(connector,
+					     new_conn_state->writeback_job);
+		}
+	}
+}
+
 /**
  * drm_atomic_helper_commit_modeset_enables - modeset commit to enable outputs
  * @dev: DRM device
@@ -1238,6 +1260,8 @@ void drm_atomic_helper_commit_modeset_enables(struct drm_device *dev,
 
 		drm_bridge_enable(encoder->bridge);
 	}
+
+	commit_writebacks(dev, old_state);
 }
 EXPORT_SYMBOL(drm_atomic_helper_commit_modeset_enables);
 
@@ -3627,6 +3651,9 @@ __drm_atomic_helper_connector_duplicate_state(struct drm_connector *connector,
 	if (state->crtc)
 		drm_connector_get(connector);
 	state->commit = NULL;
+
+	/* Don't copy over a writeback job, they are used only once */
+	state->writeback_job = NULL;
 }
 EXPORT_SYMBOL(__drm_atomic_helper_connector_duplicate_state);
 
@@ -3756,6 +3783,9 @@ __drm_atomic_helper_connector_destroy_state(struct drm_connector_state *state)
 
 	if (state->commit)
 		drm_crtc_commit_put(state->commit);
+
+	if (state->writeback_job)
+		drm_writeback_cleanup_job(state->writeback_job);
 }
 EXPORT_SYMBOL(__drm_atomic_helper_connector_destroy_state);
 
diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c
index 16b9c3810af2..add47f06ae70 100644
--- a/drivers/gpu/drm/drm_connector.c
+++ b/drivers/gpu/drm/drm_connector.c
@@ -87,6 +87,7 @@ static struct drm_conn_prop_enum_list drm_connector_enum_list[] = {
 	{ DRM_MODE_CONNECTOR_VIRTUAL, "Virtual" },
 	{ DRM_MODE_CONNECTOR_DSI, "DSI" },
 	{ DRM_MODE_CONNECTOR_DPI, "DPI" },
+	{ DRM_MODE_CONNECTOR_WRITEBACK, "Writeback" },
 };
 
 void drm_connector_ida_init(void)
@@ -249,7 +250,8 @@ int drm_connector_init(struct drm_device *dev,
 	config->num_connector++;
 	spin_unlock_irq(&config->connector_list_lock);
 
-	if (connector_type != DRM_MODE_CONNECTOR_VIRTUAL)
+	if (connector_type != DRM_MODE_CONNECTOR_VIRTUAL &&
+	    connector_type != DRM_MODE_CONNECTOR_WRITEBACK)
 		drm_object_attach_property(&connector->base,
 					      config->edid_property,
 					      0);
diff --git a/drivers/gpu/drm/drm_writeback.c b/drivers/gpu/drm/drm_writeback.c
new file mode 100644
index 000000000000..da61f929cbc3
--- /dev/null
+++ b/drivers/gpu/drm/drm_writeback.c
@@ -0,0 +1,257 @@
+/*
+ * (C) COPYRIGHT 2016 ARM Limited. All rights reserved.
+ * Author: Brian Starkey <brian.starkey@arm.com>
+ *
+ * This program is free software and is provided to you under the terms of the
+ * GNU General Public License version 2 as published by the Free Software
+ * Foundation, and any use by you of this program is subject to the terms
+ * of such GNU licence.
+ */
+
+#include <drm/drm_crtc.h>
+#include <drm/drm_modeset_helper_vtables.h>
+#include <drm/drm_property.h>
+#include <drm/drm_writeback.h>
+#include <drm/drmP.h>
+
+/**
+ * DOC: overview
+ *
+ * Writeback connectors are used to expose hardware which can write the output
+ * from a CRTC to a memory buffer. They are used and act similarly to other
+ * types of connectors, with some important differences:
+ *  - Writeback connectors don't provide a way to output visually to the user.
+ *  - Writeback connectors should always report as "disconnected" (so that
+ *    clients which don't understand them will ignore them).
+ *  - Writeback connectors don't have EDID.
+ *
+ * A framebuffer may only be attached to a writeback connector when the
+ * connector is attached to a CRTC. The WRITEBACK_FB_ID property which sets the
+ * framebuffer applies only to a single commit (see below). A framebuffer may
+ * not be attached while the CRTC is off.
+ *
+ * Writeback connectors have some additional properties, which userspace
+ * can use to query and control them:
+ *
+ *  "WRITEBACK_FB_ID":
+ *	Write-only object property storing a DRM_MODE_OBJECT_FB: it stores the
+ *	framebuffer to be written by the writeback connector. This property is
+ *	similar to the FB_ID property on planes, but will always read as zero
+ *	and is not preserved across commits.
+ *	Userspace must set this property to an output buffer every time it
+ *	wishes the buffer to get filled.
+ *
+ *  "WRITEBACK_PIXEL_FORMATS":
+ *	Immutable blob property to store the supported pixel formats table. The
+ *	data is an array of u32 DRM_FORMAT_* fourcc values.
+ *	Userspace can use this blob to find out what pixel formats are supported
+ *	by the connector's writeback engine.
+ */
+
+static bool create_writeback_properties(struct drm_device *dev)
+{
+	struct drm_property *prop;
+
+	if (!dev->mode_config.writeback_fb_id_property) {
+		prop = drm_property_create_object(dev, DRM_MODE_PROP_ATOMIC,
+						  "WRITEBACK_FB_ID",
+						  DRM_MODE_OBJECT_FB);
+		if (!prop)
+			return false;
+		dev->mode_config.writeback_fb_id_property = prop;
+	}
+
+	if (!dev->mode_config.writeback_pixel_formats_property) {
+		prop = drm_property_create(dev, DRM_MODE_PROP_BLOB | DRM_MODE_PROP_IMMUTABLE,
+					   "WRITEBACK_PIXEL_FORMATS", 0);
+		if (!prop)
+			return false;
+		dev->mode_config.writeback_pixel_formats_property = prop;
+	}
+
+	return true;
+}
+
+static const struct drm_encoder_funcs drm_writeback_encoder_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
+/**
+ * drm_writeback_connector_init - Initialize a writeback connector and its properties
+ * @dev: DRM device
+ * @wb_connector: Writeback connector to initialize
+ * @con_funcs: Connector funcs vtable
+ * @enc_helper_funcs: Encoder helper funcs vtable to be used by the internal encoder
+ * @formats: Array of supported pixel formats for the writeback engine
+ * @n_formats: Length of the formats array
+ *
+ * This function creates the writeback-connector-specific properties if they
+ * have not been already created, initializes the connector as
+ * type DRM_MODE_CONNECTOR_WRITEBACK, and correctly initializes the property
+ * values. It will also create an internal encoder associated with the
+ * drm_writeback_connector and set it to use the @enc_helper_funcs vtable for
+ * the encoder helper.
+ *
+ * Drivers should always use this function instead of drm_connector_init() to
+ * set up writeback connectors.
+ *
+ * Returns: 0 on success, or a negative error code
+ */
+int drm_writeback_connector_init(struct drm_device *dev,
+				 struct drm_writeback_connector *wb_connector,
+				 const struct drm_connector_funcs *con_funcs,
+				 const struct drm_encoder_helper_funcs *enc_helper_funcs,
+				 const u32 *formats, int n_formats)
+{
+	int ret;
+	struct drm_property_blob *blob;
+	struct drm_connector *connector = &wb_connector->base;
+	struct drm_mode_config *config = &dev->mode_config;
+
+	if (!create_writeback_properties(dev))
+		return -EINVAL;
+
+	blob = drm_property_create_blob(dev, n_formats * sizeof(*formats),
+					formats);
+	if (IS_ERR(blob))
+		return PTR_ERR(blob);
+
+	drm_encoder_helper_add(&wb_connector->encoder, enc_helper_funcs);
+	ret = drm_encoder_init(dev, &wb_connector->encoder,
+			       &drm_writeback_encoder_funcs,
+			       DRM_MODE_ENCODER_VIRTUAL, NULL);
+	if (ret)
+		goto fail;
+
+	connector->interlace_allowed = 0;
+
+	ret = drm_connector_init(dev, connector, con_funcs,
+				 DRM_MODE_CONNECTOR_WRITEBACK);
+	if (ret)
+		goto connector_fail;
+
+	ret = drm_mode_connector_attach_encoder(connector,
+						&wb_connector->encoder);
+	if (ret)
+		goto attach_fail;
+
+	INIT_LIST_HEAD(&wb_connector->job_queue);
+	spin_lock_init(&wb_connector->job_lock);
+
+	drm_object_attach_property(&connector->base,
+				   config->writeback_fb_id_property, 0);
+
+	drm_object_attach_property(&connector->base,
+				   config->writeback_pixel_formats_property,
+				   blob->base.id);
+	wb_connector->pixel_formats_blob_ptr = blob;
+
+	return 0;
+
+attach_fail:
+	drm_connector_cleanup(connector);
+connector_fail:
+	drm_encoder_cleanup(&wb_connector->encoder);
+fail:
+	drm_property_unreference_blob(blob);
+	return ret;
+}
+EXPORT_SYMBOL(drm_writeback_connector_init);
+
+/**
+ * drm_writeback_queue_job - Queue a writeback job for later signalling
+ * @wb_connector: The writeback connector to queue a job on
+ * @job: The job to queue
+ *
+ * This function adds a job to the job_queue for a writeback connector. It
+ * should be considered to take ownership of the writeback job, and so any other
+ * references to the job must be cleared after calling this function.
+ *
+ * Drivers must ensure that for a given writeback connector, jobs are queued in
+ * exactly the same order as they will be completed by the hardware (and
+ * signaled via drm_writeback_signal_completion).
+ *
+ * For every call to drm_writeback_queue_job() there must be exactly one call to
+ * drm_writeback_signal_completion()
+ *
+ * See also: drm_writeback_signal_completion()
+ */
+void drm_writeback_queue_job(struct drm_writeback_connector *wb_connector,
+			     struct drm_writeback_job *job)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&wb_connector->job_lock, flags);
+	list_add_tail(&job->list_entry, &wb_connector->job_queue);
+	spin_unlock_irqrestore(&wb_connector->job_lock, flags);
+}
+EXPORT_SYMBOL(drm_writeback_queue_job);
+
+/**
+ * drm_writeback_cleanup_job - Cleanup and free a writeback job
+ * @job: The writeback job to free
+ *
+ * Drops any references held by the writeback job, and frees the structure.
+ */
+void drm_writeback_cleanup_job(struct drm_writeback_job *job)
+{
+	if (!job)
+		return;
+
+	if (job->fb)
+		drm_framebuffer_unreference(job->fb);
+	kfree(job);
+}
+EXPORT_SYMBOL(drm_writeback_cleanup_job);
+
+/*
+ * @cleanup_work: deferred cleanup of a writeback job
+ *
+ * The job cannot be cleaned up directly in drm_writeback_signal_completion,
+ * because it may be called in interrupt context. Dropping the framebuffer
+ * reference can sleep, and so the cleanup is deferred to a workqueue.
+ */
+static void cleanup_work(struct work_struct *work)
+{
+	struct drm_writeback_job *job = container_of(work,
+						     struct drm_writeback_job,
+						     cleanup_work);
+	drm_writeback_cleanup_job(job);
+}
+
+/**
+ * drm_writeback_signal_completion - Signal the completion of a writeback job
+ * @wb_connector: The writeback connector whose job is complete
+ *
+ * Drivers should call this to signal the completion of a previously queued
+ * writeback job. It should be called as soon as possible after the hardware
+ * has finished writing, and may be called from interrupt context.
+ * It is the driver's responsibility to ensure that for a given connector, the
+ * hardware completes writeback jobs in the same order as they are queued.
+ *
+ * Unless the driver is holding its own reference to the framebuffer, it must
+ * not be accessed after calling this function.
+ *
+ * See also: drm_writeback_queue_job()
+ */
+void
+drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector)
+{
+	unsigned long flags;
+	struct drm_writeback_job *job;
+
+	spin_lock_irqsave(&wb_connector->job_lock, flags);
+	job = list_first_entry_or_null(&wb_connector->job_queue,
+				       struct drm_writeback_job,
+				       list_entry);
+	if (job)
+		list_del(&job->list_entry);
+	spin_unlock_irqrestore(&wb_connector->job_lock, flags);
+
+	if (WARN_ON(!job))
+		return;
+
+	INIT_WORK(&job->cleanup_work, cleanup_work);
+	queue_work(system_long_wq, &job->cleanup_work);
+}
+EXPORT_SYMBOL(drm_writeback_signal_completion);
diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
index cf13842a6dbd..d7b0263cc5cf 100644
--- a/include/drm/drm_atomic.h
+++ b/include/drm/drm_atomic.h
@@ -594,6 +594,9 @@ void drm_atomic_set_fence_for_plane(struct drm_plane_state *plane_state,
 int __must_check
 drm_atomic_set_crtc_for_connector(struct drm_connector_state *conn_state,
 				  struct drm_crtc *crtc);
+int drm_atomic_set_writeback_fb_for_connector(
+		struct drm_connector_state *conn_state,
+		struct drm_framebuffer *fb);
 int __must_check
 drm_atomic_add_affected_connectors(struct drm_atomic_state *state,
 				   struct drm_crtc *crtc);
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
index 758a176e7b57..8701ebcc68b3 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -425,6 +425,19 @@ struct drm_connector_state {
 	 * protection. This is most commonly used for HDCP.
 	 */
 	unsigned int content_protection;
+
+	/**
+	 * @writeback_job: Writeback job for writeback connectors
+	 *
+	 * Holds the framebuffer for a writeback connector. As the writeback
+	 * completion may be asynchronous to the normal commit cycle, the
+	 * writeback job lifetime is managed separately from the normal atomic
+	 * state by this object.
+	 *
+	 * See also: drm_writeback_queue_job() and
+	 * drm_writeback_signal_completion()
+	 */
+	struct drm_writeback_job *writeback_job;
 };
 
 /**
diff --git a/include/drm/drm_mode_config.h b/include/drm/drm_mode_config.h
index 7569f22ffef6..c012e1148ec0 100644
--- a/include/drm/drm_mode_config.h
+++ b/include/drm/drm_mode_config.h
@@ -779,6 +779,20 @@ struct drm_mode_config {
 	 */
 	struct drm_property *panel_orientation_property;
 
+	/**
+	 * @writeback_fb_id_property: Property for writeback connectors, storing
+	 * the ID of the output framebuffer.
+	 * See also: drm_writeback_connector_init()
+	 */
+	struct drm_property *writeback_fb_id_property;
+	/**
+	 * @writeback_pixel_formats_property: Property for writeback connectors,
+	 * storing an array of the supported pixel formats for the writeback
+	 * engine (read-only).
+	 * See also: drm_writeback_connector_init()
+	 */
+	struct drm_property *writeback_pixel_formats_property;
+
 	/* dumb ioctl parameters */
 	uint32_t preferred_depth, prefer_shadow;
 
diff --git a/include/drm/drm_modeset_helper_vtables.h b/include/drm/drm_modeset_helper_vtables.h
index 3e76ca805b0f..97d3a810bc85 100644
--- a/include/drm/drm_modeset_helper_vtables.h
+++ b/include/drm/drm_modeset_helper_vtables.h
@@ -974,6 +974,17 @@ struct drm_connector_helper_funcs {
 	 */
 	int (*atomic_check)(struct drm_connector *connector,
 			    struct drm_connector_state *state);
+
+	/**
+	 * @atomic_commit:
+	 *
+	 * For write-back connectors, this is the point to commit the write-
+	 * back job to hw.
+	 *
+	 * This callback is used by the atomic modeset helpers.
+	 */
+	void (*atomic_commit)(struct drm_connector *connector,
+			      struct drm_writeback_job *writeback_job);
 };
 
 /**
diff --git a/include/drm/drm_writeback.h b/include/drm/drm_writeback.h
new file mode 100644
index 000000000000..0bb95fd4907d
--- /dev/null
+++ b/include/drm/drm_writeback.h
@@ -0,0 +1,89 @@
+/*
+ * (C) COPYRIGHT 2016 ARM Limited. All rights reserved.
+ * Author: Brian Starkey <brian.starkey@arm.com>
+ *
+ * This program is free software and is provided to you under the terms of the
+ * GNU General Public License version 2 as published by the Free Software
+ * Foundation, and any use by you of this program is subject to the terms
+ * of such GNU licence.
+ */
+
+#ifndef __DRM_WRITEBACK_H__
+#define __DRM_WRITEBACK_H__
+#include <drm/drm_connector.h>
+#include <drm/drm_encoder.h>
+#include <linux/workqueue.h>
+
+struct drm_writeback_connector {
+	struct drm_connector base;
+
+	/**
+	 * @encoder: Internal encoder used by the connector to fulfill
+	 * the DRM framework requirements. The users of the
+	 * @drm_writeback_connector control the behaviour of the @encoder
+	 * by passing the @enc_funcs parameter to drm_writeback_connector_init()
+	 * function.
+	 */
+	struct drm_encoder encoder;
+
+	/**
+	 * @pixel_formats_blob_ptr:
+	 *
+	 * DRM blob property data for the pixel formats list on writeback
+	 * connectors
+	 * See also drm_writeback_connector_init()
+	 */
+	struct drm_property_blob *pixel_formats_blob_ptr;
+
+	/** @job_lock: Protects job_queue */
+	spinlock_t job_lock;
+
+	/**
+	 * @job_queue:
+	 *
+	 * Holds a list of a connector's writeback jobs; the last item is the
+	 * most recent. The first item may be either waiting for the hardware
+	 * to begin writing, or currently being written.
+	 *
+	 * See also: drm_writeback_queue_job() and
+	 * drm_writeback_signal_completion()
+	 */
+	struct list_head job_queue;
+};
+#define to_wb_connector(x) container_of(x, struct drm_writeback_connector, base)
+
+struct drm_writeback_job {
+	/**
+	 * @cleanup_work:
+	 *
+	 * Used to allow drm_writeback_signal_completion to defer dropping the
+	 * framebuffer reference to a workqueue.
+	 */
+	struct work_struct cleanup_work;
+	/**
+	 * @list_entry:
+	 *
+	 * List item for the connector's @job_queue
+	 */
+	struct list_head list_entry;
+	/**
+	 * @fb:
+	 *
+	 * Framebuffer to be written to by the writeback connector. Do not set
+	 * directly, use drm_atomic_set_writeback_fb_for_connector()
+	 */
+	struct drm_framebuffer *fb;
+};
+
+int drm_writeback_connector_init(struct drm_device *dev,
+				 struct drm_writeback_connector *wb_connector,
+				 const struct drm_connector_funcs *con_funcs,
+				 const struct drm_encoder_helper_funcs *enc_helper_funcs,
+				 const u32 *formats, int n_formats);
+
+void drm_writeback_queue_job(struct drm_writeback_connector *wb_connector,
+			     struct drm_writeback_job *job);
+
+void drm_writeback_cleanup_job(struct drm_writeback_job *job);
+void drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector);
+#endif
diff --git a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h
index 2c575794fb52..7b47e184e95e 100644
--- a/include/uapi/drm/drm_mode.h
+++ b/include/uapi/drm/drm_mode.h
@@ -338,6 +338,7 @@ enum drm_mode_subconnector {
 #define DRM_MODE_CONNECTOR_VIRTUAL      15
 #define DRM_MODE_CONNECTOR_DSI		16
 #define DRM_MODE_CONNECTOR_DPI		17
+#define DRM_MODE_CONNECTOR_WRITEBACK	18
 
 struct drm_mode_get_connector {
 
-- 
2.14.3

^ permalink raw reply related	[flat|nested] 41+ messages in thread

* [RFC 2/4] drm: writeback: Add out-fences for writeback connectors
  2018-02-23 13:17 [RFC 0/4] drm/msm/mdp5: writeback connector support Rob Clark
@ 2018-02-23 13:17     ` Rob Clark
  0 siblings, 0 replies; 41+ messages in thread
From: Rob Clark @ 2018-02-23 13:17 UTC (permalink / raw)
  To: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW
  Cc: Maarten Lankhorst, linux-arm-msm-u79uwXL29TY76Z2rM5mHXA,
	Liviu Dudau, linux-kernel-u79uwXL29TY76Z2rM5mHXA, David Airlie,
	Rob Clark, Sean Paul, Gustavo Padovan, Mihail Atanassov,
	freedreno-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, Brian Starkey

From: Brian Starkey <brian.starkey@arm.com>

Add the WRITEBACK_OUT_FENCE_PTR property to writeback connectors, to
enable userspace to get a fence which will signal once the writeback is
complete. It is not allowed to request an out-fence without a
framebuffer attached to the connector.

A timeline is added to drm_writeback_connector for use by the writeback
out-fences.

In the case of a commit failure or DRM_MODE_ATOMIC_TEST_ONLY, the fence
is set to -1.

Changes from v2:
 - Rebase onto Gustavo Padovan's v9 explicit sync series
 - Change out_fence_ptr type to s32 __user *
 - Set *out_fence_ptr to -1 in drm_atomic_connector_set_property
 - Store fence in drm_writeback_job
 Gustavo Padovan:
 - Move out_fence_ptr out of connector_state
 - Signal fence from drm_writeback_signal_completion instead of
   in driver directly

Changes from v3:
 - Rebase onto 7e9081c5aac7 drm/fence: fix memory overwrite when setting out_fence fd
   (change out_fence_ptr to s32 __user *, for real this time.)
 - Update documentation around WRITEBACK_OUT_FENCE_PTR

Signed-off-by: Brian Starkey <brian.starkey@arm.com>
[rebased and fixed conflicts]
Signed-off-by: Mihail Atanassov <mihail.atanassov@arm.com>
Signed-off-by: Liviu Dudau <liviu.dudau@arm.com>
Signed-off-by: Rob Clark <robdclark@gmail.com>
---
 drivers/gpu/drm/drm_atomic.c    |  99 ++++++++++++++++++++++++++++++++----
 drivers/gpu/drm/drm_writeback.c | 109 +++++++++++++++++++++++++++++++++++++++-
 include/drm/drm_atomic.h        |   8 +++
 include/drm/drm_connector.h     |   8 +--
 include/drm/drm_mode_config.h   |   8 +++
 include/drm/drm_writeback.h     |  41 ++++++++++++++-
 6 files changed, 257 insertions(+), 16 deletions(-)

diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
index 019f131fe8be..fc8c4da409ff 100644
--- a/drivers/gpu/drm/drm_atomic.c
+++ b/drivers/gpu/drm/drm_atomic.c
@@ -318,6 +318,35 @@ static s32 __user *get_out_fence_for_crtc(struct drm_atomic_state *state,
 	return fence_ptr;
 }
 
+static int set_out_fence_for_connector(struct drm_atomic_state *state,
+					struct drm_connector *connector,
+					s32 __user *fence_ptr)
+{
+	unsigned int index = drm_connector_index(connector);
+
+	if (!fence_ptr)
+		return 0;
+
+	if (put_user(-1, fence_ptr))
+		return -EFAULT;
+
+	state->connectors[index].out_fence_ptr = fence_ptr;
+
+	return 0;
+}
+
+static s32 __user *get_out_fence_for_connector(struct drm_atomic_state *state,
+					       struct drm_connector *connector)
+{
+	unsigned int index = drm_connector_index(connector);
+	s32 __user *fence_ptr;
+
+	fence_ptr = state->connectors[index].out_fence_ptr;
+	state->connectors[index].out_fence_ptr = NULL;
+
+	return fence_ptr;
+}
+
 /**
  * drm_atomic_set_mode_for_crtc - set mode for CRTC
  * @state: the CRTC whose incoming state to update
@@ -676,6 +705,12 @@ static int drm_atomic_connector_check(struct drm_connector *connector,
 		return -EINVAL;
 	}
 
+	if (writeback_job->out_fence && !writeback_job->fb) {
+		DRM_DEBUG_ATOMIC("[CONNECTOR:%d:%s] requesting out-fence without framebuffer\n",
+				 connector->base.id, connector->name);
+		return -EINVAL;
+	}
+
 	return 0;
 }
 
@@ -1277,6 +1312,11 @@ static int drm_atomic_connector_set_property(struct drm_connector *connector,
 		if (fb)
 			drm_framebuffer_unreference(fb);
 		return ret;
+	} else if (property == config->writeback_out_fence_ptr_property) {
+		s32 __user *fence_ptr = u64_to_user_ptr(val);
+
+		return set_out_fence_for_connector(state->state, connector,
+						   fence_ptr);
 	} else if (connector->funcs->atomic_set_property) {
 		return connector->funcs->atomic_set_property(connector,
 				state, property, val);
@@ -1361,6 +1401,8 @@ drm_atomic_connector_get_property(struct drm_connector *connector,
 	} else if (property == config->writeback_fb_id_property) {
 		/* Writeback framebuffer is one-shot, write and forget */
 		*val = 0;
+	} else if (property == config->writeback_out_fence_ptr_property) {
+		*val = 0;
 	} else if (connector->funcs->atomic_get_property) {
 		return connector->funcs->atomic_get_property(connector,
 				state, property, val);
@@ -2221,7 +2263,7 @@ static int setup_out_fence(struct drm_out_fence_state *fence_state,
 	return 0;
 }
 
-static int prepare_crtc_signaling(struct drm_device *dev,
+static int prepare_signaling(struct drm_device *dev,
 				  struct drm_atomic_state *state,
 				  struct drm_mode_atomic *arg,
 				  struct drm_file *file_priv,
@@ -2230,6 +2272,8 @@ static int prepare_crtc_signaling(struct drm_device *dev,
 {
 	struct drm_crtc *crtc;
 	struct drm_crtc_state *crtc_state;
+	struct drm_connector *conn;
+	struct drm_connector_state *conn_state;
 	int i, c = 0, ret;
 
 	if (arg->flags & DRM_MODE_ATOMIC_TEST_ONLY)
@@ -2295,6 +2339,43 @@ static int prepare_crtc_signaling(struct drm_device *dev,
 		c++;
 	}
 
+	for_each_new_connector_in_state(state, conn, conn_state, i) {
+		struct drm_writeback_job *job;
+		struct drm_out_fence_state *f;
+		struct dma_fence *fence;
+		s32 __user *fence_ptr;
+
+		fence_ptr = get_out_fence_for_connector(state, conn);
+		if (!fence_ptr)
+			continue;
+
+		job = drm_atomic_get_writeback_job(conn_state);
+		if (!job)
+			return -ENOMEM;
+
+		f = krealloc(*fence_state, sizeof(**fence_state) *
+			     (*num_fences + 1), GFP_KERNEL);
+		if (!f)
+			return -ENOMEM;
+
+		memset(&f[*num_fences], 0, sizeof(*f));
+
+		f[*num_fences].out_fence_ptr = fence_ptr;
+		*fence_state = f;
+
+		fence = drm_writeback_get_out_fence((struct drm_writeback_connector *)conn);
+		if (!fence)
+			return -ENOMEM;
+
+		ret = setup_out_fence(&f[(*num_fences)++], fence);
+		if (ret) {
+			dma_fence_put(fence);
+			return ret;
+		}
+
+		job->out_fence = fence;
+	}
+
 	/*
 	 * Having this flag means user mode pends on event which will never
 	 * reach due to lack of at least one CRTC for signaling
@@ -2305,11 +2386,11 @@ static int prepare_crtc_signaling(struct drm_device *dev,
 	return 0;
 }
 
-static void complete_crtc_signaling(struct drm_device *dev,
-				    struct drm_atomic_state *state,
-				    struct drm_out_fence_state *fence_state,
-				    unsigned int num_fences,
-				    bool install_fds)
+static void complete_signaling(struct drm_device *dev,
+			       struct drm_atomic_state *state,
+			       struct drm_out_fence_state *fence_state,
+			       unsigned int num_fences,
+			       bool install_fds)
 {
 	struct drm_crtc *crtc;
 	struct drm_crtc_state *crtc_state;
@@ -2488,8 +2569,8 @@ int drm_mode_atomic_ioctl(struct drm_device *dev,
 		drm_mode_object_put(obj);
 	}
 
-	ret = prepare_crtc_signaling(dev, state, arg, file_priv, &fence_state,
-				     &num_fences);
+	ret = prepare_signaling(dev, state, arg, file_priv, &fence_state,
+				&num_fences);
 	if (ret)
 		goto out;
 
@@ -2507,7 +2588,7 @@ int drm_mode_atomic_ioctl(struct drm_device *dev,
 out:
 	drm_atomic_clean_old_fb(dev, plane_mask, ret);
 
-	complete_crtc_signaling(dev, state, fence_state, num_fences, !ret);
+	complete_signaling(dev, state, fence_state, num_fences, !ret);
 
 	if (ret == -EDEADLK) {
 		drm_atomic_state_clear(state);
diff --git a/drivers/gpu/drm/drm_writeback.c b/drivers/gpu/drm/drm_writeback.c
index da61f929cbc3..839e79932c19 100644
--- a/drivers/gpu/drm/drm_writeback.c
+++ b/drivers/gpu/drm/drm_writeback.c
@@ -13,6 +13,7 @@
 #include <drm/drm_property.h>
 #include <drm/drm_writeback.h>
 #include <drm/drmP.h>
+#include <linux/dma-fence.h>
 
 /**
  * DOC: overview
@@ -30,6 +31,16 @@
  * framebuffer applies only to a single commit (see below). A framebuffer may
  * not be attached while the CRTC is off.
  *
+ * Unlike with planes, when a writeback framebuffer is removed by userspace DRM
+ * makes no attempt to remove it from active use by the connector. This is
+ * because no method is provided to abort a writeback operation, and in any
+ * case making a new commit whilst a writeback is ongoing is undefined (see
+ * WRITEBACK_OUT_FENCE_PTR below). As soon as the current writeback is finished,
+ * the framebuffer will automatically no longer be in active use. As it will
+ * also have already been removed from the framebuffer list, there will be no
+ * way for any userspace application to retrieve a reference to it in the
+ * intervening period.
+ *
  * Writeback connectors have some additional properties, which userspace
  * can use to query and control them:
  *
@@ -46,8 +57,54 @@
  *	data is an array of u32 DRM_FORMAT_* fourcc values.
  *	Userspace can use this blob to find out what pixel formats are supported
  *	by the connector's writeback engine.
+ *
+ *  "WRITEBACK_OUT_FENCE_PTR":
+ *	Userspace can use this property to provide a pointer for the kernel to
+ *	fill with a sync_file file descriptor, which will signal once the
+ *	writeback is finished. The value should be the address of a 32-bit
+ *	signed integer, cast to a u64.
+ *	Userspace should wait for this fence to signal before making another
+ *	commit affecting any of the same CRTCs, Planes or Connectors.
+ *	**Failure to do so will result in undefined behaviour.**
+ *	For this reason it is strongly recommended that all userspace
+ *	applications making use of writeback connectors *always* retrieve an
+ *	out-fence for the commit and use it appropriately.
+ *	From userspace, this property will always read as zero.
  */
 
+#define fence_to_wb_connector(x) container_of(x->lock, \
+					      struct drm_writeback_connector, \
+					      fence_lock)
+
+static const char *drm_writeback_fence_get_driver_name(struct dma_fence *fence)
+{
+	struct drm_writeback_connector *wb_connector =
+		fence_to_wb_connector(fence);
+
+	return wb_connector->base.dev->driver->name;
+}
+
+static const char *
+drm_writeback_fence_get_timeline_name(struct dma_fence *fence)
+{
+	struct drm_writeback_connector *wb_connector =
+		fence_to_wb_connector(fence);
+
+	return wb_connector->timeline_name;
+}
+
+static bool drm_writeback_fence_enable_signaling(struct dma_fence *fence)
+{
+	return true;
+}
+
+static const struct dma_fence_ops drm_writeback_fence_ops = {
+	.get_driver_name = drm_writeback_fence_get_driver_name,
+	.get_timeline_name = drm_writeback_fence_get_timeline_name,
+	.enable_signaling = drm_writeback_fence_enable_signaling,
+	.wait = dma_fence_default_wait,
+};
+
 static bool create_writeback_properties(struct drm_device *dev)
 {
 	struct drm_property *prop;
@@ -69,6 +126,15 @@ static bool create_writeback_properties(struct drm_device *dev)
 		dev->mode_config.writeback_pixel_formats_property = prop;
 	}
 
+	if (!dev->mode_config.writeback_out_fence_ptr_property) {
+		prop = drm_property_create_range(dev, DRM_MODE_PROP_ATOMIC,
+						 "WRITEBACK_OUT_FENCE_PTR", 0,
+						 U64_MAX);
+		if (!prop)
+			return -ENOMEM;
+		dev->mode_config.writeback_out_fence_ptr_property = prop;
+	}
+
 	return true;
 }
 
@@ -138,6 +204,15 @@ int drm_writeback_connector_init(struct drm_device *dev,
 	INIT_LIST_HEAD(&wb_connector->job_queue);
 	spin_lock_init(&wb_connector->job_lock);
 
+	wb_connector->fence_context = dma_fence_context_alloc(1);
+	spin_lock_init(&wb_connector->fence_lock);
+	snprintf(wb_connector->timeline_name,
+		 sizeof(wb_connector->timeline_name),
+		 "CONNECTOR:%d-%s", connector->base.id, connector->name);
+
+	drm_object_attach_property(&connector->base,
+				   config->writeback_out_fence_ptr_property, 0);
+
 	drm_object_attach_property(&connector->base,
 				   config->writeback_fb_id_property, 0);
 
@@ -200,6 +275,7 @@ void drm_writeback_cleanup_job(struct drm_writeback_job *job)
 
 	if (job->fb)
 		drm_framebuffer_unreference(job->fb);
+	dma_fence_put(job->out_fence);
 	kfree(job);
 }
 EXPORT_SYMBOL(drm_writeback_cleanup_job);
@@ -222,6 +298,7 @@ static void cleanup_work(struct work_struct *work)
 /**
  * drm_writeback_signal_completion - Signal the completion of a writeback job
  * @wb_connector: The writeback connector whose job is complete
+ * @status: Status code to set in the writeback out_fence (0 for success)
  *
  * Drivers should call this to signal the completion of a previously queued
  * writeback job. It should be called as soon as possible after the hardware
@@ -235,7 +312,8 @@ static void cleanup_work(struct work_struct *work)
  * See also: drm_writeback_queue_job()
  */
 void
-drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector)
+drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector,
+				int status)
 {
 	unsigned long flags;
 	struct drm_writeback_job *job;
@@ -244,8 +322,14 @@ drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector)
 	job = list_first_entry_or_null(&wb_connector->job_queue,
 				       struct drm_writeback_job,
 				       list_entry);
-	if (job)
+	if (job) {
 		list_del(&job->list_entry);
+		if (job->out_fence) {
+			if (status)
+				dma_fence_set_error(job->out_fence, status);
+			dma_fence_signal(job->out_fence);
+		}
+	}
 	spin_unlock_irqrestore(&wb_connector->job_lock, flags);
 
 	if (WARN_ON(!job))
@@ -255,3 +339,24 @@ drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector)
 	queue_work(system_long_wq, &job->cleanup_work);
 }
 EXPORT_SYMBOL(drm_writeback_signal_completion);
+
+struct dma_fence *
+drm_writeback_get_out_fence(struct drm_writeback_connector *wb_connector)
+{
+	struct dma_fence *fence;
+
+	if (WARN_ON(wb_connector->base.connector_type !=
+		    DRM_MODE_CONNECTOR_WRITEBACK))
+		return NULL;
+
+	fence = kzalloc(sizeof(*fence), GFP_KERNEL);
+	if (!fence)
+		return NULL;
+
+	dma_fence_init(fence, &drm_writeback_fence_ops,
+		       &wb_connector->fence_lock, wb_connector->fence_context,
+		       ++wb_connector->fence_seqno);
+
+	return fence;
+}
+EXPORT_SYMBOL(drm_writeback_get_out_fence);
diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
index d7b0263cc5cf..09076a625637 100644
--- a/include/drm/drm_atomic.h
+++ b/include/drm/drm_atomic.h
@@ -160,6 +160,14 @@ struct __drm_crtcs_state {
 struct __drm_connnectors_state {
 	struct drm_connector *ptr;
 	struct drm_connector_state *state, *old_state, *new_state;
+	/**
+	 * @out_fence_ptr:
+	 *
+	 * User-provided pointer which the kernel uses to return a sync_file
+	 * file descriptor. Used by writeback connectors to signal completion of
+	 * the writeback.
+	 */
+	s32 __user *out_fence_ptr;
 };
 
 struct drm_private_obj;
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
index 8701ebcc68b3..da56fda6e218 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -429,10 +429,10 @@ struct drm_connector_state {
 	/**
 	 * @writeback_job: Writeback job for writeback connectors
 	 *
-	 * Holds the framebuffer for a writeback connector. As the writeback
-	 * completion may be asynchronous to the normal commit cycle, the
-	 * writeback job lifetime is managed separately from the normal atomic
-	 * state by this object.
+	 * Holds the framebuffer and out-fence for a writeback connector. As
+	 * the writeback completion may be asynchronous to the normal commit
+	 * cycle, the writeback job lifetime is managed separately from the
+	 * normal atomic state by this object.
 	 *
 	 * See also: drm_writeback_queue_job() and
 	 * drm_writeback_signal_completion()
diff --git a/include/drm/drm_mode_config.h b/include/drm/drm_mode_config.h
index c012e1148ec0..930e00560380 100644
--- a/include/drm/drm_mode_config.h
+++ b/include/drm/drm_mode_config.h
@@ -792,6 +792,14 @@ struct drm_mode_config {
 	 * See also: drm_writeback_connector_init()
 	 */
 	struct drm_property *writeback_pixel_formats_property;
+	/**
+	 * @writeback_out_fence_ptr_property: Property for writeback connectors,
+	 * fd pointer representing the outgoing fences for a writeback
+	 * connector. Userspace should provide a pointer to a value of type s32,
+	 * and then cast that pointer to u64.
+	 * See also: drm_writeback_connector_init()
+	 */
+	struct drm_property *writeback_out_fence_ptr_property;
 
 	/* dumb ioctl parameters */
 	uint32_t preferred_depth, prefer_shadow;
diff --git a/include/drm/drm_writeback.h b/include/drm/drm_writeback.h
index 0bb95fd4907d..b9470ae2a53d 100644
--- a/include/drm/drm_writeback.h
+++ b/include/drm/drm_writeback.h
@@ -49,6 +49,32 @@ struct drm_writeback_connector {
 	 * drm_writeback_signal_completion()
 	 */
 	struct list_head job_queue;
+
+	/**
+	 * @fence_context:
+	 *
+	 * timeline context used for fence operations.
+	 */
+	unsigned int fence_context;
+	/**
+	 * @fence_lock:
+	 *
+	 * spinlock to protect the fences in the fence_context.
+	 */
+	spinlock_t fence_lock;
+	/**
+	 * @fence_seqno:
+	 *
+	 * Seqno variable used as monotonic counter for the fences
+	 * created on the connector's timeline.
+	 */
+	unsigned long fence_seqno;
+	/**
+	 * @timeline_name:
+	 *
+	 * The name of the connector's fence timeline.
+	 */
+	char timeline_name[32];
 };
 #define to_wb_connector(x) container_of(x, struct drm_writeback_connector, base)
 
@@ -73,6 +99,13 @@ struct drm_writeback_job {
 	 * directly, use drm_atomic_set_writeback_fb_for_connector()
 	 */
 	struct drm_framebuffer *fb;
+
+	/**
+	 * @out_fence:
+	 *
+	 * Fence which will signal once the writeback has completed
+	 */
+	struct dma_fence *out_fence;
 };
 
 int drm_writeback_connector_init(struct drm_device *dev,
@@ -85,5 +118,11 @@ void drm_writeback_queue_job(struct drm_writeback_connector *wb_connector,
 			     struct drm_writeback_job *job);
 
 void drm_writeback_cleanup_job(struct drm_writeback_job *job);
-void drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector);
+
+void
+drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector,
+				int status);
+
+struct dma_fence *
+drm_writeback_get_out_fence(struct drm_writeback_connector *wb_connector);
 #endif
-- 
2.14.3

_______________________________________________
Freedreno mailing list
Freedreno@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/freedreno

^ permalink raw reply related	[flat|nested] 41+ messages in thread

* [RFC 2/4] drm: writeback: Add out-fences for writeback connectors
@ 2018-02-23 13:17     ` Rob Clark
  0 siblings, 0 replies; 41+ messages in thread
From: Rob Clark @ 2018-02-23 13:17 UTC (permalink / raw)
  To: dri-devel
  Cc: freedreno, linux-arm-msm, Brian Starkey, Liviu Dudau,
	Mihail Atanassov, Rob Clark, Gustavo Padovan, Maarten Lankhorst,
	Sean Paul, David Airlie, linux-kernel

From: Brian Starkey <brian.starkey@arm.com>

Add the WRITEBACK_OUT_FENCE_PTR property to writeback connectors, to
enable userspace to get a fence which will signal once the writeback is
complete. It is not allowed to request an out-fence without a
framebuffer attached to the connector.

A timeline is added to drm_writeback_connector for use by the writeback
out-fences.

In the case of a commit failure or DRM_MODE_ATOMIC_TEST_ONLY, the fence
is set to -1.

Changes from v2:
 - Rebase onto Gustavo Padovan's v9 explicit sync series
 - Change out_fence_ptr type to s32 __user *
 - Set *out_fence_ptr to -1 in drm_atomic_connector_set_property
 - Store fence in drm_writeback_job
 Gustavo Padovan:
 - Move out_fence_ptr out of connector_state
 - Signal fence from drm_writeback_signal_completion instead of
   in driver directly

Changes from v3:
 - Rebase onto 7e9081c5aac7 drm/fence: fix memory overwrite when setting out_fence fd
   (change out_fence_ptr to s32 __user *, for real this time.)
 - Update documentation around WRITEBACK_OUT_FENCE_PTR

Signed-off-by: Brian Starkey <brian.starkey@arm.com>
[rebased and fixed conflicts]
Signed-off-by: Mihail Atanassov <mihail.atanassov@arm.com>
Signed-off-by: Liviu Dudau <liviu.dudau@arm.com>
Signed-off-by: Rob Clark <robdclark@gmail.com>
---
 drivers/gpu/drm/drm_atomic.c    |  99 ++++++++++++++++++++++++++++++++----
 drivers/gpu/drm/drm_writeback.c | 109 +++++++++++++++++++++++++++++++++++++++-
 include/drm/drm_atomic.h        |   8 +++
 include/drm/drm_connector.h     |   8 +--
 include/drm/drm_mode_config.h   |   8 +++
 include/drm/drm_writeback.h     |  41 ++++++++++++++-
 6 files changed, 257 insertions(+), 16 deletions(-)

diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
index 019f131fe8be..fc8c4da409ff 100644
--- a/drivers/gpu/drm/drm_atomic.c
+++ b/drivers/gpu/drm/drm_atomic.c
@@ -318,6 +318,35 @@ static s32 __user *get_out_fence_for_crtc(struct drm_atomic_state *state,
 	return fence_ptr;
 }
 
+static int set_out_fence_for_connector(struct drm_atomic_state *state,
+					struct drm_connector *connector,
+					s32 __user *fence_ptr)
+{
+	unsigned int index = drm_connector_index(connector);
+
+	if (!fence_ptr)
+		return 0;
+
+	if (put_user(-1, fence_ptr))
+		return -EFAULT;
+
+	state->connectors[index].out_fence_ptr = fence_ptr;
+
+	return 0;
+}
+
+static s32 __user *get_out_fence_for_connector(struct drm_atomic_state *state,
+					       struct drm_connector *connector)
+{
+	unsigned int index = drm_connector_index(connector);
+	s32 __user *fence_ptr;
+
+	fence_ptr = state->connectors[index].out_fence_ptr;
+	state->connectors[index].out_fence_ptr = NULL;
+
+	return fence_ptr;
+}
+
 /**
  * drm_atomic_set_mode_for_crtc - set mode for CRTC
  * @state: the CRTC whose incoming state to update
@@ -676,6 +705,12 @@ static int drm_atomic_connector_check(struct drm_connector *connector,
 		return -EINVAL;
 	}
 
+	if (writeback_job->out_fence && !writeback_job->fb) {
+		DRM_DEBUG_ATOMIC("[CONNECTOR:%d:%s] requesting out-fence without framebuffer\n",
+				 connector->base.id, connector->name);
+		return -EINVAL;
+	}
+
 	return 0;
 }
 
@@ -1277,6 +1312,11 @@ static int drm_atomic_connector_set_property(struct drm_connector *connector,
 		if (fb)
 			drm_framebuffer_unreference(fb);
 		return ret;
+	} else if (property == config->writeback_out_fence_ptr_property) {
+		s32 __user *fence_ptr = u64_to_user_ptr(val);
+
+		return set_out_fence_for_connector(state->state, connector,
+						   fence_ptr);
 	} else if (connector->funcs->atomic_set_property) {
 		return connector->funcs->atomic_set_property(connector,
 				state, property, val);
@@ -1361,6 +1401,8 @@ drm_atomic_connector_get_property(struct drm_connector *connector,
 	} else if (property == config->writeback_fb_id_property) {
 		/* Writeback framebuffer is one-shot, write and forget */
 		*val = 0;
+	} else if (property == config->writeback_out_fence_ptr_property) {
+		*val = 0;
 	} else if (connector->funcs->atomic_get_property) {
 		return connector->funcs->atomic_get_property(connector,
 				state, property, val);
@@ -2221,7 +2263,7 @@ static int setup_out_fence(struct drm_out_fence_state *fence_state,
 	return 0;
 }
 
-static int prepare_crtc_signaling(struct drm_device *dev,
+static int prepare_signaling(struct drm_device *dev,
 				  struct drm_atomic_state *state,
 				  struct drm_mode_atomic *arg,
 				  struct drm_file *file_priv,
@@ -2230,6 +2272,8 @@ static int prepare_crtc_signaling(struct drm_device *dev,
 {
 	struct drm_crtc *crtc;
 	struct drm_crtc_state *crtc_state;
+	struct drm_connector *conn;
+	struct drm_connector_state *conn_state;
 	int i, c = 0, ret;
 
 	if (arg->flags & DRM_MODE_ATOMIC_TEST_ONLY)
@@ -2295,6 +2339,43 @@ static int prepare_crtc_signaling(struct drm_device *dev,
 		c++;
 	}
 
+	for_each_new_connector_in_state(state, conn, conn_state, i) {
+		struct drm_writeback_job *job;
+		struct drm_out_fence_state *f;
+		struct dma_fence *fence;
+		s32 __user *fence_ptr;
+
+		fence_ptr = get_out_fence_for_connector(state, conn);
+		if (!fence_ptr)
+			continue;
+
+		job = drm_atomic_get_writeback_job(conn_state);
+		if (!job)
+			return -ENOMEM;
+
+		f = krealloc(*fence_state, sizeof(**fence_state) *
+			     (*num_fences + 1), GFP_KERNEL);
+		if (!f)
+			return -ENOMEM;
+
+		memset(&f[*num_fences], 0, sizeof(*f));
+
+		f[*num_fences].out_fence_ptr = fence_ptr;
+		*fence_state = f;
+
+		fence = drm_writeback_get_out_fence((struct drm_writeback_connector *)conn);
+		if (!fence)
+			return -ENOMEM;
+
+		ret = setup_out_fence(&f[(*num_fences)++], fence);
+		if (ret) {
+			dma_fence_put(fence);
+			return ret;
+		}
+
+		job->out_fence = fence;
+	}
+
 	/*
 	 * Having this flag means user mode pends on event which will never
 	 * reach due to lack of at least one CRTC for signaling
@@ -2305,11 +2386,11 @@ static int prepare_crtc_signaling(struct drm_device *dev,
 	return 0;
 }
 
-static void complete_crtc_signaling(struct drm_device *dev,
-				    struct drm_atomic_state *state,
-				    struct drm_out_fence_state *fence_state,
-				    unsigned int num_fences,
-				    bool install_fds)
+static void complete_signaling(struct drm_device *dev,
+			       struct drm_atomic_state *state,
+			       struct drm_out_fence_state *fence_state,
+			       unsigned int num_fences,
+			       bool install_fds)
 {
 	struct drm_crtc *crtc;
 	struct drm_crtc_state *crtc_state;
@@ -2488,8 +2569,8 @@ int drm_mode_atomic_ioctl(struct drm_device *dev,
 		drm_mode_object_put(obj);
 	}
 
-	ret = prepare_crtc_signaling(dev, state, arg, file_priv, &fence_state,
-				     &num_fences);
+	ret = prepare_signaling(dev, state, arg, file_priv, &fence_state,
+				&num_fences);
 	if (ret)
 		goto out;
 
@@ -2507,7 +2588,7 @@ int drm_mode_atomic_ioctl(struct drm_device *dev,
 out:
 	drm_atomic_clean_old_fb(dev, plane_mask, ret);
 
-	complete_crtc_signaling(dev, state, fence_state, num_fences, !ret);
+	complete_signaling(dev, state, fence_state, num_fences, !ret);
 
 	if (ret == -EDEADLK) {
 		drm_atomic_state_clear(state);
diff --git a/drivers/gpu/drm/drm_writeback.c b/drivers/gpu/drm/drm_writeback.c
index da61f929cbc3..839e79932c19 100644
--- a/drivers/gpu/drm/drm_writeback.c
+++ b/drivers/gpu/drm/drm_writeback.c
@@ -13,6 +13,7 @@
 #include <drm/drm_property.h>
 #include <drm/drm_writeback.h>
 #include <drm/drmP.h>
+#include <linux/dma-fence.h>
 
 /**
  * DOC: overview
@@ -30,6 +31,16 @@
  * framebuffer applies only to a single commit (see below). A framebuffer may
  * not be attached while the CRTC is off.
  *
+ * Unlike with planes, when a writeback framebuffer is removed by userspace DRM
+ * makes no attempt to remove it from active use by the connector. This is
+ * because no method is provided to abort a writeback operation, and in any
+ * case making a new commit whilst a writeback is ongoing is undefined (see
+ * WRITEBACK_OUT_FENCE_PTR below). As soon as the current writeback is finished,
+ * the framebuffer will automatically no longer be in active use. As it will
+ * also have already been removed from the framebuffer list, there will be no
+ * way for any userspace application to retrieve a reference to it in the
+ * intervening period.
+ *
  * Writeback connectors have some additional properties, which userspace
  * can use to query and control them:
  *
@@ -46,8 +57,54 @@
  *	data is an array of u32 DRM_FORMAT_* fourcc values.
  *	Userspace can use this blob to find out what pixel formats are supported
  *	by the connector's writeback engine.
+ *
+ *  "WRITEBACK_OUT_FENCE_PTR":
+ *	Userspace can use this property to provide a pointer for the kernel to
+ *	fill with a sync_file file descriptor, which will signal once the
+ *	writeback is finished. The value should be the address of a 32-bit
+ *	signed integer, cast to a u64.
+ *	Userspace should wait for this fence to signal before making another
+ *	commit affecting any of the same CRTCs, Planes or Connectors.
+ *	**Failure to do so will result in undefined behaviour.**
+ *	For this reason it is strongly recommended that all userspace
+ *	applications making use of writeback connectors *always* retrieve an
+ *	out-fence for the commit and use it appropriately.
+ *	From userspace, this property will always read as zero.
  */
 
+#define fence_to_wb_connector(x) container_of(x->lock, \
+					      struct drm_writeback_connector, \
+					      fence_lock)
+
+static const char *drm_writeback_fence_get_driver_name(struct dma_fence *fence)
+{
+	struct drm_writeback_connector *wb_connector =
+		fence_to_wb_connector(fence);
+
+	return wb_connector->base.dev->driver->name;
+}
+
+static const char *
+drm_writeback_fence_get_timeline_name(struct dma_fence *fence)
+{
+	struct drm_writeback_connector *wb_connector =
+		fence_to_wb_connector(fence);
+
+	return wb_connector->timeline_name;
+}
+
+static bool drm_writeback_fence_enable_signaling(struct dma_fence *fence)
+{
+	return true;
+}
+
+static const struct dma_fence_ops drm_writeback_fence_ops = {
+	.get_driver_name = drm_writeback_fence_get_driver_name,
+	.get_timeline_name = drm_writeback_fence_get_timeline_name,
+	.enable_signaling = drm_writeback_fence_enable_signaling,
+	.wait = dma_fence_default_wait,
+};
+
 static bool create_writeback_properties(struct drm_device *dev)
 {
 	struct drm_property *prop;
@@ -69,6 +126,15 @@ static bool create_writeback_properties(struct drm_device *dev)
 		dev->mode_config.writeback_pixel_formats_property = prop;
 	}
 
+	if (!dev->mode_config.writeback_out_fence_ptr_property) {
+		prop = drm_property_create_range(dev, DRM_MODE_PROP_ATOMIC,
+						 "WRITEBACK_OUT_FENCE_PTR", 0,
+						 U64_MAX);
+		if (!prop)
+			return -ENOMEM;
+		dev->mode_config.writeback_out_fence_ptr_property = prop;
+	}
+
 	return true;
 }
 
@@ -138,6 +204,15 @@ int drm_writeback_connector_init(struct drm_device *dev,
 	INIT_LIST_HEAD(&wb_connector->job_queue);
 	spin_lock_init(&wb_connector->job_lock);
 
+	wb_connector->fence_context = dma_fence_context_alloc(1);
+	spin_lock_init(&wb_connector->fence_lock);
+	snprintf(wb_connector->timeline_name,
+		 sizeof(wb_connector->timeline_name),
+		 "CONNECTOR:%d-%s", connector->base.id, connector->name);
+
+	drm_object_attach_property(&connector->base,
+				   config->writeback_out_fence_ptr_property, 0);
+
 	drm_object_attach_property(&connector->base,
 				   config->writeback_fb_id_property, 0);
 
@@ -200,6 +275,7 @@ void drm_writeback_cleanup_job(struct drm_writeback_job *job)
 
 	if (job->fb)
 		drm_framebuffer_unreference(job->fb);
+	dma_fence_put(job->out_fence);
 	kfree(job);
 }
 EXPORT_SYMBOL(drm_writeback_cleanup_job);
@@ -222,6 +298,7 @@ static void cleanup_work(struct work_struct *work)
 /**
  * drm_writeback_signal_completion - Signal the completion of a writeback job
  * @wb_connector: The writeback connector whose job is complete
+ * @status: Status code to set in the writeback out_fence (0 for success)
  *
  * Drivers should call this to signal the completion of a previously queued
  * writeback job. It should be called as soon as possible after the hardware
@@ -235,7 +312,8 @@ static void cleanup_work(struct work_struct *work)
  * See also: drm_writeback_queue_job()
  */
 void
-drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector)
+drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector,
+				int status)
 {
 	unsigned long flags;
 	struct drm_writeback_job *job;
@@ -244,8 +322,14 @@ drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector)
 	job = list_first_entry_or_null(&wb_connector->job_queue,
 				       struct drm_writeback_job,
 				       list_entry);
-	if (job)
+	if (job) {
 		list_del(&job->list_entry);
+		if (job->out_fence) {
+			if (status)
+				dma_fence_set_error(job->out_fence, status);
+			dma_fence_signal(job->out_fence);
+		}
+	}
 	spin_unlock_irqrestore(&wb_connector->job_lock, flags);
 
 	if (WARN_ON(!job))
@@ -255,3 +339,24 @@ drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector)
 	queue_work(system_long_wq, &job->cleanup_work);
 }
 EXPORT_SYMBOL(drm_writeback_signal_completion);
+
+struct dma_fence *
+drm_writeback_get_out_fence(struct drm_writeback_connector *wb_connector)
+{
+	struct dma_fence *fence;
+
+	if (WARN_ON(wb_connector->base.connector_type !=
+		    DRM_MODE_CONNECTOR_WRITEBACK))
+		return NULL;
+
+	fence = kzalloc(sizeof(*fence), GFP_KERNEL);
+	if (!fence)
+		return NULL;
+
+	dma_fence_init(fence, &drm_writeback_fence_ops,
+		       &wb_connector->fence_lock, wb_connector->fence_context,
+		       ++wb_connector->fence_seqno);
+
+	return fence;
+}
+EXPORT_SYMBOL(drm_writeback_get_out_fence);
diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
index d7b0263cc5cf..09076a625637 100644
--- a/include/drm/drm_atomic.h
+++ b/include/drm/drm_atomic.h
@@ -160,6 +160,14 @@ struct __drm_crtcs_state {
 struct __drm_connnectors_state {
 	struct drm_connector *ptr;
 	struct drm_connector_state *state, *old_state, *new_state;
+	/**
+	 * @out_fence_ptr:
+	 *
+	 * User-provided pointer which the kernel uses to return a sync_file
+	 * file descriptor. Used by writeback connectors to signal completion of
+	 * the writeback.
+	 */
+	s32 __user *out_fence_ptr;
 };
 
 struct drm_private_obj;
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
index 8701ebcc68b3..da56fda6e218 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -429,10 +429,10 @@ struct drm_connector_state {
 	/**
 	 * @writeback_job: Writeback job for writeback connectors
 	 *
-	 * Holds the framebuffer for a writeback connector. As the writeback
-	 * completion may be asynchronous to the normal commit cycle, the
-	 * writeback job lifetime is managed separately from the normal atomic
-	 * state by this object.
+	 * Holds the framebuffer and out-fence for a writeback connector. As
+	 * the writeback completion may be asynchronous to the normal commit
+	 * cycle, the writeback job lifetime is managed separately from the
+	 * normal atomic state by this object.
 	 *
 	 * See also: drm_writeback_queue_job() and
 	 * drm_writeback_signal_completion()
diff --git a/include/drm/drm_mode_config.h b/include/drm/drm_mode_config.h
index c012e1148ec0..930e00560380 100644
--- a/include/drm/drm_mode_config.h
+++ b/include/drm/drm_mode_config.h
@@ -792,6 +792,14 @@ struct drm_mode_config {
 	 * See also: drm_writeback_connector_init()
 	 */
 	struct drm_property *writeback_pixel_formats_property;
+	/**
+	 * @writeback_out_fence_ptr_property: Property for writeback connectors,
+	 * fd pointer representing the outgoing fences for a writeback
+	 * connector. Userspace should provide a pointer to a value of type s32,
+	 * and then cast that pointer to u64.
+	 * See also: drm_writeback_connector_init()
+	 */
+	struct drm_property *writeback_out_fence_ptr_property;
 
 	/* dumb ioctl parameters */
 	uint32_t preferred_depth, prefer_shadow;
diff --git a/include/drm/drm_writeback.h b/include/drm/drm_writeback.h
index 0bb95fd4907d..b9470ae2a53d 100644
--- a/include/drm/drm_writeback.h
+++ b/include/drm/drm_writeback.h
@@ -49,6 +49,32 @@ struct drm_writeback_connector {
 	 * drm_writeback_signal_completion()
 	 */
 	struct list_head job_queue;
+
+	/**
+	 * @fence_context:
+	 *
+	 * timeline context used for fence operations.
+	 */
+	unsigned int fence_context;
+	/**
+	 * @fence_lock:
+	 *
+	 * spinlock to protect the fences in the fence_context.
+	 */
+	spinlock_t fence_lock;
+	/**
+	 * @fence_seqno:
+	 *
+	 * Seqno variable used as monotonic counter for the fences
+	 * created on the connector's timeline.
+	 */
+	unsigned long fence_seqno;
+	/**
+	 * @timeline_name:
+	 *
+	 * The name of the connector's fence timeline.
+	 */
+	char timeline_name[32];
 };
 #define to_wb_connector(x) container_of(x, struct drm_writeback_connector, base)
 
@@ -73,6 +99,13 @@ struct drm_writeback_job {
 	 * directly, use drm_atomic_set_writeback_fb_for_connector()
 	 */
 	struct drm_framebuffer *fb;
+
+	/**
+	 * @out_fence:
+	 *
+	 * Fence which will signal once the writeback has completed
+	 */
+	struct dma_fence *out_fence;
 };
 
 int drm_writeback_connector_init(struct drm_device *dev,
@@ -85,5 +118,11 @@ void drm_writeback_queue_job(struct drm_writeback_connector *wb_connector,
 			     struct drm_writeback_job *job);
 
 void drm_writeback_cleanup_job(struct drm_writeback_job *job);
-void drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector);
+
+void
+drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector,
+				int status);
+
+struct dma_fence *
+drm_writeback_get_out_fence(struct drm_writeback_connector *wb_connector);
 #endif
-- 
2.14.3

^ permalink raw reply related	[flat|nested] 41+ messages in thread

* [RFC 3/4] drm/msm/mdp5: add config for writeback pipes
  2018-02-23 13:17 [RFC 0/4] drm/msm/mdp5: writeback connector support Rob Clark
@ 2018-02-23 13:17     ` Rob Clark
  0 siblings, 0 replies; 41+ messages in thread
From: Rob Clark @ 2018-02-23 13:17 UTC (permalink / raw)
  To: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW
  Cc: Archit Taneja, Neil Armstrong, David Airlie,
	linux-arm-msm-u79uwXL29TY76Z2rM5mHXA, Liviu Dudau,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA, Rob Clark, Sean Paul,
	Daniel Vetter, freedreno-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	Brian Starkey, Ville Syrjälä

Note there seems to be a slight disagreement between public 8x16 HRD
(which claims WB2 has offset of 0x65000, relative to start of MDP), and
reality (which claims WB2 has offset of 0x64800).  I sided with reality.

There should also be a WB0 attached to LM0 (which routes to DSI
interface).  It isn't clear if this can be used at the same time as
output to DSI, which would be hugely useful.  I was unable to get this
to work (with HDMI bridge chip on db410c, so DSI in video mode).

This will be needed to implement writeback support, but also useful
to remove a manual hack to the generated headers (since rnndb register
docs for WB had been merged long ago).

Also fixes LM3 offset.

Signed-off-by: Rob Clark <robdclark@gmail.com>
---
 drivers/gpu/drm/msm/disp/mdp5/mdp5.xml.h |  2 --
 drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.c | 17 +++++++++++------
 drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.h | 11 +++++++++++
 drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c |  1 +
 4 files changed, 23 insertions(+), 8 deletions(-)

diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5.xml.h b/drivers/gpu/drm/msm/disp/mdp5/mdp5.xml.h
index d9c10e02ee41..bebcbabb1fe4 100644
--- a/drivers/gpu/drm/msm/disp/mdp5/mdp5.xml.h
+++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5.xml.h
@@ -1391,13 +1391,11 @@ static inline uint32_t REG_MDP5_PP_FBC_LOSSY_MODE(uint32_t i0) { return 0x000000
 static inline uint32_t __offset_WB(uint32_t idx)
 {
 	switch (idx) {
-#if 0  /* TEMPORARY until patch that adds wb.base[] is merged */
 		case 0: return (mdp5_cfg->wb.base[0]);
 		case 1: return (mdp5_cfg->wb.base[1]);
 		case 2: return (mdp5_cfg->wb.base[2]);
 		case 3: return (mdp5_cfg->wb.base[3]);
 		case 4: return (mdp5_cfg->wb.base[4]);
-#endif
 		default: return INVALID_IDX(idx);
 	}
 }
diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.c
index 824067d2d427..f92e68cdeeef 100644
--- a/drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.c
+++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.c
@@ -300,8 +300,8 @@ const struct mdp5_cfg_hw msm8x16_config = {
 		},
 	},
 	.ctl = {
-		.count = 5,
-		.base = { 0x01000, 0x01200, 0x01400, 0x01600, 0x01800 },
+		.count = 3,
+		.base = { 0x01000, 0x01200, 0x01400 },
 		.flush_hw_mask = 0x4003ffff,
 	},
 	.pipe_vig = {
@@ -324,7 +324,7 @@ const struct mdp5_cfg_hw msm8x16_config = {
 	},
 	.lm = {
 		.count = 2, /* LM0 and LM3 */
-		.base = { 0x44000, 0x47000 },
+		.base = { [0] = 0x44000, [3] = 0x47000 },
 		.instances = {
 				{ .id = 0, .pp = 0, .dspp = 0,
 				  .caps = MDP_LM_CAP_DISPLAY, },
@@ -338,12 +338,17 @@ const struct mdp5_cfg_hw msm8x16_config = {
 	.dspp = {
 		.count = 1,
 		.base = { 0x54000 },
-
+	},
+	.wb = {
+		.count = 1,
+		.base = { [0] = 0x64000, [2] = 0x64800 },
+		.instances = {
+			{ .id = 2, .lm = 3 },
+		},
 	},
 	.intf = {
-		.base = { 0x00000, 0x6a800 },
+		.base = { 0x6a000, 0x6a800, 0x6b000, 0x6b800 },
 		.connect = {
-			[0] = INTF_DISABLED,
 			[1] = INTF_DSI,
 		},
 	},
diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.h b/drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.h
index 75910d0f2f4c..2e529fb2f9ee 100644
--- a/drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.h
+++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.h
@@ -77,6 +77,16 @@ struct mdp5_mdp_block {
 	uint32_t caps;			/* MDP capabilities: MDP_CAP_xxx bits */
 };
 
+struct mdp5_wb_instance {
+	int id;
+	int lm;
+};
+
+struct mdp5_wb_block {
+	MDP5_SUB_BLOCK_DEFINITION;
+	struct mdp5_wb_instance instances[MAX_BASES];
+};
+
 #define MDP5_INTF_NUM_MAX	5
 
 struct mdp5_intf_block {
@@ -100,6 +110,7 @@ struct mdp5_cfg_hw {
 	struct mdp5_sub_block pp;
 	struct mdp5_sub_block dsc;
 	struct mdp5_sub_block cdm;
+	struct mdp5_wb_block wb;
 	struct mdp5_intf_block intf;
 
 	uint32_t max_clk;
diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c
index 6d8e3a9a6fc0..1f44d8f15ce1 100644
--- a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c
+++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c
@@ -652,6 +652,7 @@ struct msm_kms *mdp5_kms_init(struct drm_device *dev)
 	pm_runtime_get_sync(&pdev->dev);
 	for (i = 0; i < MDP5_INTF_NUM_MAX; i++) {
 		if (mdp5_cfg_intf_is_virtual(config->hw->intf.connect[i]) ||
+		    (config->hw->intf.connect[i] == INTF_DISABLED) ||
 		    !config->hw->intf.base[i])
 			continue;
 		mdp5_write(mdp5_kms, REG_MDP5_INTF_TIMING_ENGINE_EN(i), 0);
-- 
2.14.3

_______________________________________________
Freedreno mailing list
Freedreno@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/freedreno

^ permalink raw reply related	[flat|nested] 41+ messages in thread

* [RFC 3/4] drm/msm/mdp5: add config for writeback pipes
@ 2018-02-23 13:17     ` Rob Clark
  0 siblings, 0 replies; 41+ messages in thread
From: Rob Clark @ 2018-02-23 13:17 UTC (permalink / raw)
  To: dri-devel
  Cc: freedreno, linux-arm-msm, Brian Starkey, Liviu Dudau, Rob Clark,
	David Airlie, Sean Paul, Archit Taneja, Daniel Vetter,
	Neil Armstrong, Ville Syrjälä,
	linux-kernel

Note there seems to be a slight disagreement between public 8x16 HRD
(which claims WB2 has offset of 0x65000, relative to start of MDP), and
reality (which claims WB2 has offset of 0x64800).  I sided with reality.

There should also be a WB0 attached to LM0 (which routes to DSI
interface).  It isn't clear if this can be used at the same time as
output to DSI, which would be hugely useful.  I was unable to get this
to work (with HDMI bridge chip on db410c, so DSI in video mode).

This will be needed to implement writeback support, but also useful
to remove a manual hack to the generated headers (since rnndb register
docs for WB had been merged long ago).

Also fixes LM3 offset.

Signed-off-by: Rob Clark <robdclark@gmail.com>
---
 drivers/gpu/drm/msm/disp/mdp5/mdp5.xml.h |  2 --
 drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.c | 17 +++++++++++------
 drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.h | 11 +++++++++++
 drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c |  1 +
 4 files changed, 23 insertions(+), 8 deletions(-)

diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5.xml.h b/drivers/gpu/drm/msm/disp/mdp5/mdp5.xml.h
index d9c10e02ee41..bebcbabb1fe4 100644
--- a/drivers/gpu/drm/msm/disp/mdp5/mdp5.xml.h
+++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5.xml.h
@@ -1391,13 +1391,11 @@ static inline uint32_t REG_MDP5_PP_FBC_LOSSY_MODE(uint32_t i0) { return 0x000000
 static inline uint32_t __offset_WB(uint32_t idx)
 {
 	switch (idx) {
-#if 0  /* TEMPORARY until patch that adds wb.base[] is merged */
 		case 0: return (mdp5_cfg->wb.base[0]);
 		case 1: return (mdp5_cfg->wb.base[1]);
 		case 2: return (mdp5_cfg->wb.base[2]);
 		case 3: return (mdp5_cfg->wb.base[3]);
 		case 4: return (mdp5_cfg->wb.base[4]);
-#endif
 		default: return INVALID_IDX(idx);
 	}
 }
diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.c
index 824067d2d427..f92e68cdeeef 100644
--- a/drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.c
+++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.c
@@ -300,8 +300,8 @@ const struct mdp5_cfg_hw msm8x16_config = {
 		},
 	},
 	.ctl = {
-		.count = 5,
-		.base = { 0x01000, 0x01200, 0x01400, 0x01600, 0x01800 },
+		.count = 3,
+		.base = { 0x01000, 0x01200, 0x01400 },
 		.flush_hw_mask = 0x4003ffff,
 	},
 	.pipe_vig = {
@@ -324,7 +324,7 @@ const struct mdp5_cfg_hw msm8x16_config = {
 	},
 	.lm = {
 		.count = 2, /* LM0 and LM3 */
-		.base = { 0x44000, 0x47000 },
+		.base = { [0] = 0x44000, [3] = 0x47000 },
 		.instances = {
 				{ .id = 0, .pp = 0, .dspp = 0,
 				  .caps = MDP_LM_CAP_DISPLAY, },
@@ -338,12 +338,17 @@ const struct mdp5_cfg_hw msm8x16_config = {
 	.dspp = {
 		.count = 1,
 		.base = { 0x54000 },
-
+	},
+	.wb = {
+		.count = 1,
+		.base = { [0] = 0x64000, [2] = 0x64800 },
+		.instances = {
+			{ .id = 2, .lm = 3 },
+		},
 	},
 	.intf = {
-		.base = { 0x00000, 0x6a800 },
+		.base = { 0x6a000, 0x6a800, 0x6b000, 0x6b800 },
 		.connect = {
-			[0] = INTF_DISABLED,
 			[1] = INTF_DSI,
 		},
 	},
diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.h b/drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.h
index 75910d0f2f4c..2e529fb2f9ee 100644
--- a/drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.h
+++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.h
@@ -77,6 +77,16 @@ struct mdp5_mdp_block {
 	uint32_t caps;			/* MDP capabilities: MDP_CAP_xxx bits */
 };
 
+struct mdp5_wb_instance {
+	int id;
+	int lm;
+};
+
+struct mdp5_wb_block {
+	MDP5_SUB_BLOCK_DEFINITION;
+	struct mdp5_wb_instance instances[MAX_BASES];
+};
+
 #define MDP5_INTF_NUM_MAX	5
 
 struct mdp5_intf_block {
@@ -100,6 +110,7 @@ struct mdp5_cfg_hw {
 	struct mdp5_sub_block pp;
 	struct mdp5_sub_block dsc;
 	struct mdp5_sub_block cdm;
+	struct mdp5_wb_block wb;
 	struct mdp5_intf_block intf;
 
 	uint32_t max_clk;
diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c
index 6d8e3a9a6fc0..1f44d8f15ce1 100644
--- a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c
+++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c
@@ -652,6 +652,7 @@ struct msm_kms *mdp5_kms_init(struct drm_device *dev)
 	pm_runtime_get_sync(&pdev->dev);
 	for (i = 0; i < MDP5_INTF_NUM_MAX; i++) {
 		if (mdp5_cfg_intf_is_virtual(config->hw->intf.connect[i]) ||
+		    (config->hw->intf.connect[i] == INTF_DISABLED) ||
 		    !config->hw->intf.base[i])
 			continue;
 		mdp5_write(mdp5_kms, REG_MDP5_INTF_TIMING_ENGINE_EN(i), 0);
-- 
2.14.3

^ permalink raw reply related	[flat|nested] 41+ messages in thread

* [RFC 4/4] drm/msm/mdp5: writeback support
  2018-02-23 13:17 [RFC 0/4] drm/msm/mdp5: writeback connector support Rob Clark
@ 2018-02-23 13:17     ` Rob Clark
  0 siblings, 0 replies; 41+ messages in thread
From: Rob Clark @ 2018-02-23 13:17 UTC (permalink / raw)
  To: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW
  Cc: Archit Taneja, Laurent Pinchart, Neil Armstrong, David Airlie,
	linux-arm-msm-u79uwXL29TY76Z2rM5mHXA, Liviu Dudau,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA, Sushmita Susheelendra,
	Rob Clark, Sean Paul, Daniel Vetter,
	freedreno-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, Brian Starkey,
	Ville Syrjälä

In a way, based on the original writeback patch from Jilai Wang, but a
lot has shifted around since then.

Signed-off-by: Rob Clark <robdclark@gmail.com>
---
 drivers/gpu/drm/msm/Makefile              |   1 +
 drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c |  23 +-
 drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c  |  38 +++-
 drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.h  |   7 +-
 drivers/gpu/drm/msm/disp/mdp5/mdp5_wb.c   | 367 ++++++++++++++++++++++++++++++
 drivers/gpu/drm/msm/dsi/dsi_host.c        |   4 +-
 6 files changed, 431 insertions(+), 9 deletions(-)
 create mode 100644 drivers/gpu/drm/msm/disp/mdp5/mdp5_wb.c

diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
index cd40c050b2d7..c9f50adef2db 100644
--- a/drivers/gpu/drm/msm/Makefile
+++ b/drivers/gpu/drm/msm/Makefile
@@ -45,6 +45,7 @@ msm-y := \
 	disp/mdp5/mdp5_mixer.o \
 	disp/mdp5/mdp5_plane.o \
 	disp/mdp5/mdp5_smp.o \
+	disp/mdp5/mdp5_wb.o \
 	msm_atomic.o \
 	msm_debugfs.o \
 	msm_drv.o \
diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c
index 9893e43ba6c5..b00ca88b741d 100644
--- a/drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c
+++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c
@@ -484,7 +484,11 @@ static void mdp5_crtc_atomic_enable(struct drm_crtc *crtc,
 	}
 
 	/* Restore vblank irq handling after power is enabled */
-	drm_crtc_vblank_on(crtc);
+// TODO we can't ->get_scanout_pos() for wb (since virtual intf)..
+// perhaps drm core should be clever enough not to drm_reset_vblank_timestamp()
+// for virtual encoders / writeback?
+	if (mdp5_cstate->pipeline.intf->type != INTF_WB)
+		drm_crtc_vblank_on(crtc);
 
 	mdp5_crtc_mode_set_nofb(crtc);
 
@@ -518,7 +522,11 @@ int mdp5_crtc_setup_pipeline(struct drm_crtc *crtc,
 		u32 caps;
 		int ret;
 
-		caps = MDP_LM_CAP_DISPLAY;
+		if (pipeline->intf->type == INTF_WB)
+			caps = MDP_LM_CAP_WB;
+		else
+			caps = MDP_LM_CAP_DISPLAY;
+
 		if (need_right_mixer)
 			caps |= MDP_LM_CAP_PAIR;
 
@@ -545,6 +553,7 @@ int mdp5_crtc_setup_pipeline(struct drm_crtc *crtc,
 	mdp5_cstate->err_irqmask = intf2err(intf->num);
 	mdp5_cstate->vblank_irqmask = intf2vblank(pipeline->mixer, intf);
 
+// XXX should we be treating WB as cmd_mode??
 	if ((intf->type == INTF_DSI) &&
 	    (intf->mode == MDP5_INTF_DSI_MODE_COMMAND)) {
 		mdp5_cstate->pp_done_irqmask = lm2ppdone(pipeline->mixer);
@@ -639,8 +648,12 @@ static int mdp5_crtc_atomic_check(struct drm_crtc *crtc,
 	}
 
 	/* bail out early if there aren't any planes */
-	if (!cnt)
-		return 0;
+	if (!cnt) {
+		if (!state->active)
+			return 0;
+		dev_err(dev->dev, "%s has no planes!\n", crtc->name);
+		return -EINVAL;
+	}
 
 	hw_cfg = mdp5_cfg_get_hw_config(mdp5_kms->cfg);
 
@@ -1160,7 +1173,7 @@ void mdp5_crtc_wait_for_commit_done(struct drm_crtc *crtc)
 
 	if (mdp5_cstate->cmd_mode)
 		mdp5_crtc_wait_for_pp_done(crtc);
-	else
+	else if (mdp5_cstate->pipeline.intf->type != INTF_WB)
 		mdp5_crtc_wait_for_flush_done(crtc);
 }
 
diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c
index 1f44d8f15ce1..239010905637 100644
--- a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c
+++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c
@@ -427,7 +427,8 @@ static int modeset_init(struct mdp5_kms *mdp5_kms)
 	 * the MDP5 interfaces) than the number of layer mixers present in HW,
 	 * but let's be safe here anyway
 	 */
-	num_crtcs = min(priv->num_encoders, mdp5_kms->num_hwmixers);
+	num_crtcs = min(priv->num_encoders + hw_cfg->wb.count,
+			mdp5_kms->num_hwmixers);
 
 	/*
 	 * Construct planes equaling the number of hw pipes, and CRTCs for the
@@ -482,6 +483,33 @@ static int modeset_init(struct mdp5_kms *mdp5_kms)
 		encoder->possible_crtcs = (1 << priv->num_crtcs) - 1;
 	}
 
+	/*
+	 * Lastly, construct writeback connectors.
+	 */
+	for (i = 0; i < hw_cfg->wb.count; i++) {
+		struct drm_writeback_connector *wb_conn;
+		struct mdp5_ctl *ctl;
+
+		ctl = mdp5_ctlm_request(mdp5_kms->ctlm, -1);
+		if (!ctl) {
+			dev_err(dev->dev,
+				"failed to allocate ctl for writeback %d\n", i);
+			continue;
+		}
+
+		wb_conn = mdp5_wb_connector_init(dev, ctl,
+				hw_cfg->wb.instances[i].id);
+		if (IS_ERR(wb_conn)) {
+			ret = PTR_ERR(wb_conn);
+			dev_err(dev->dev,
+				"failed to construct writeback connector %d (%d)\n",
+				i, ret);
+			goto fail;
+		}
+
+		wb_conn->encoder.possible_crtcs = (1 << priv->num_crtcs) - 1;
+	}
+
 	return 0;
 
 fail:
@@ -555,6 +583,10 @@ static bool mdp5_get_scanoutpos(struct drm_device *dev, unsigned int pipe,
 		return false;
 	}
 
+	/* unsupported for writeback: */
+	if (encoder->encoder_type == DRM_MODE_ENCODER_VIRTUAL)
+		return false;
+
 	vsw = mode->crtc_vsync_end - mode->crtc_vsync_start;
 	vbp = mode->crtc_vtotal - mode->crtc_vsync_end;
 
@@ -610,6 +642,10 @@ static u32 mdp5_get_vblank_counter(struct drm_device *dev, unsigned int pipe)
 	if (!encoder)
 		return 0;
 
+	/* unsupported for writeback: */
+	if (encoder->encoder_type == DRM_MODE_ENCODER_VIRTUAL)
+		return 0;
+
 	return mdp5_encoder_get_framecount(encoder);
 }
 
diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.h b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.h
index 425a03d213e5..be0f93ef33e1 100644
--- a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.h
+++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.h
@@ -18,6 +18,8 @@
 #ifndef __MDP5_KMS_H__
 #define __MDP5_KMS_H__
 
+#include <drm/drm_writeback.h>
+
 #include "msm_drv.h"
 #include "msm_kms.h"
 #include "disp/mdp_kms.h"
@@ -251,7 +253,7 @@ static inline uint32_t intf2vblank(struct mdp5_hw_mixer *mixer,
 		return MDP5_IRQ_PING_PONG_0_RD_PTR << mixer->pp;
 
 	if (intf->type == INTF_WB)
-		return MDP5_IRQ_WB_2_DONE;
+		return MDP5_IRQ_WB_2_DONE | MDP5_IRQ_WB_0_DONE | MDP5_IRQ_WB_1_DONE;
 
 	switch (intf->num) {
 	case 0:  return MDP5_IRQ_INTF0_VSYNC;
@@ -330,4 +332,7 @@ static inline int mdp5_cmd_encoder_set_split_display(
 }
 #endif
 
+struct drm_writeback_connector *mdp5_wb_connector_init(struct drm_device *dev,
+		struct mdp5_ctl *ctl, unsigned wb_id);
+
 #endif /* __MDP5_KMS_H__ */
diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_wb.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_wb.c
new file mode 100644
index 000000000000..3dabd0a1aa8b
--- /dev/null
+++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_wb.c
@@ -0,0 +1,367 @@
+/*
+ * Copyright (C) 2018 Red Hat
+ * Author: Rob Clark <robdclark@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "mdp5_kms.h"
+
+/*
+ * Writeback connector/encoder implementation:
+ */
+
+struct mdp5_wb_connector {
+	struct drm_writeback_connector base;
+
+	u32 nformats;
+	u32 formats[32];
+
+	unsigned id;
+	struct mdp5_ctl *ctl;
+	struct mdp5_interface *intf;
+
+	struct mdp_irq wb_done;
+};
+#define to_mdp5_wb_connector(x) container_of(x, struct mdp5_wb_connector, base)
+
+
+static void mdp5_wb_connector_atomic_commit(struct drm_connector *connector,
+		struct drm_writeback_job *job);
+
+static int mdp5_wb_connector_get_modes(struct drm_connector *connector)
+{
+	struct drm_device *dev = connector->dev;
+
+	return drm_add_modes_noedid(connector, dev->mode_config.max_width,
+			dev->mode_config.max_height);
+}
+
+static enum drm_mode_status
+mdp5_wb_connector_mode_valid(struct drm_connector *connector,
+		struct drm_display_mode *mode)
+{
+	struct drm_device *dev = connector->dev;
+	struct drm_mode_config *mode_config = &dev->mode_config;
+	int w = mode->hdisplay, h = mode->vdisplay;
+
+	if ((w < mode_config->min_width) || (w > mode_config->max_width))
+		return MODE_BAD_HVALUE;
+
+	if ((h < mode_config->min_height) || (h > mode_config->max_height))
+		return MODE_BAD_VVALUE;
+
+	return MODE_OK;
+}
+
+const struct drm_connector_helper_funcs mdp5_wb_connector_helper_funcs = {
+	.get_modes = mdp5_wb_connector_get_modes,
+	.mode_valid = mdp5_wb_connector_mode_valid,
+	.atomic_commit = mdp5_wb_connector_atomic_commit,
+};
+
+static enum drm_connector_status
+mdp5_wb_connector_detect(struct drm_connector *connector, bool force)
+{
+	return connector_status_disconnected;
+}
+
+static void mdp5_wb_connector_destroy(struct drm_connector *connector)
+{
+	drm_connector_cleanup(connector);
+}
+
+static const struct drm_connector_funcs mdp5_wb_connector_funcs = {
+	.reset = drm_atomic_helper_connector_reset,
+	.detect = mdp5_wb_connector_detect,
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.destroy = mdp5_wb_connector_destroy,
+	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static int
+mdp5_wb_encoder_atomic_check(struct drm_encoder *encoder,
+		struct drm_crtc_state *crtc_state,
+		struct drm_connector_state *conn_state)
+{
+	struct msm_drm_private *priv = encoder->dev->dev_private;
+	struct mdp5_crtc_state *mdp5_cstate = to_mdp5_crtc_state(crtc_state);
+	struct mdp5_wb_connector *mdp5_wb = to_mdp5_wb_connector(
+		to_wb_connector(conn_state->connector));
+	struct drm_framebuffer *fb;
+	const struct msm_format *format;
+	const struct mdp_format *mdp_fmt;
+	struct drm_format_name_buf format_name;
+	int ret;
+
+	if (!conn_state->writeback_job || !conn_state->writeback_job->fb)
+		return 0;
+
+	fb = conn_state->writeback_job->fb;
+
+	DBG("wb[%u]: check writeback %ux%u@%s", mdp5_wb->id,
+		fb->width, fb->height,
+		drm_get_format_name(fb->format->format, &format_name));
+
+	format = mdp_get_format(priv->kms, fb->format->format);
+	if (!format) {
+		DBG("Invalid pixel format!");
+		return -EINVAL;
+	}
+
+	mdp_fmt = to_mdp_format(format);
+	if (MDP_FORMAT_IS_YUV(mdp_fmt)) {
+		switch (mdp_fmt->chroma_sample) {
+		case CHROMA_420:
+		case CHROMA_H2V1:
+			/* supported */
+			break;
+		case CHROMA_H1V2:
+		default:
+			DBG("unsupported wb chroma samp=%d\n",
+				mdp_fmt->chroma_sample);
+			return -EINVAL;
+		}
+	}
+
+	/* TODO I think we would prefer to have proper prepare_fb()/cleanup_fb()
+	 * vfuncs, as with plane..  Also, where to unprepare?
+	 */
+	ret = msm_framebuffer_prepare(fb, priv->kms->aspace);
+	if (ret)
+		return ret;
+
+	mdp5_cstate->ctl = mdp5_wb->ctl;
+	mdp5_cstate->pipeline.intf = mdp5_wb->intf;
+	mdp5_cstate->defer_start = true;
+
+	return 0;
+}
+
+static void
+wb_csc_setup(struct mdp5_kms *mdp5_kms, u32 wb_id, struct csc_cfg *csc)
+{
+	uint32_t  i;
+	uint32_t *matrix;
+
+	if (unlikely(!csc))
+		return;
+
+	matrix = csc->matrix;
+	mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_0(wb_id),
+		MDP5_WB_CSC_MATRIX_COEFF_0_COEFF_11(matrix[0]) |
+		MDP5_WB_CSC_MATRIX_COEFF_0_COEFF_12(matrix[1]));
+	mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_1(wb_id),
+		MDP5_WB_CSC_MATRIX_COEFF_1_COEFF_13(matrix[2]) |
+		MDP5_WB_CSC_MATRIX_COEFF_1_COEFF_21(matrix[3]));
+	mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_2(wb_id),
+		MDP5_WB_CSC_MATRIX_COEFF_2_COEFF_22(matrix[4]) |
+		MDP5_WB_CSC_MATRIX_COEFF_2_COEFF_23(matrix[5]));
+	mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_3(wb_id),
+		MDP5_WB_CSC_MATRIX_COEFF_3_COEFF_31(matrix[6]) |
+		MDP5_WB_CSC_MATRIX_COEFF_3_COEFF_32(matrix[7]));
+	mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_4(wb_id),
+		MDP5_WB_CSC_MATRIX_COEFF_4_COEFF_33(matrix[8]));
+
+	for (i = 0; i < ARRAY_SIZE(csc->pre_bias); i++) {
+		uint32_t *pre_clamp = csc->pre_clamp;
+		uint32_t *post_clamp = csc->post_clamp;
+
+		mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_COMP_PRECLAMP(wb_id, i),
+			MDP5_WB_CSC_COMP_PRECLAMP_REG_HIGH(pre_clamp[2*i+1]) |
+			MDP5_WB_CSC_COMP_PRECLAMP_REG_LOW(pre_clamp[2*i]));
+
+		mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_COMP_POSTCLAMP(wb_id, i),
+			MDP5_WB_CSC_COMP_POSTCLAMP_REG_HIGH(post_clamp[2*i+1]) |
+			MDP5_WB_CSC_COMP_POSTCLAMP_REG_LOW(post_clamp[2*i]));
+
+		mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_COMP_PREBIAS(wb_id, i),
+			MDP5_WB_CSC_COMP_PREBIAS_REG_VALUE(csc->pre_bias[i]));
+
+		mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_COMP_POSTBIAS(wb_id, i),
+			MDP5_WB_CSC_COMP_POSTBIAS_REG_VALUE(csc->post_bias[i]));
+	}
+}
+
+static void
+mdp5_wb_connector_atomic_commit(struct drm_connector *connector,
+		struct drm_writeback_job *job)
+{
+	struct msm_drm_private *priv = connector->dev->dev_private;
+	struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(priv->kms));
+	struct drm_connector_state *conn_state = connector->state;
+	struct drm_writeback_connector *wb_conn = to_wb_connector(connector);
+	struct mdp5_crtc_state *mdp5_crtc_state =
+		to_mdp5_crtc_state(wb_conn->encoder.crtc->state);
+	struct mdp5_wb_connector *mdp5_wb = to_mdp5_wb_connector(wb_conn);
+	struct drm_framebuffer *fb = job->fb;
+	struct drm_format_name_buf format_name;
+	const struct mdp_format *fmt =
+		to_mdp_format(mdp_get_format(priv->kms, fb->format->format));
+	u32 ystride0, ystride1, outsize;
+	u32 dst_format, pattern, opmode = 0;
+
+	DBG("wb[%u]: kick writeback %ux%u@%s", mdp5_wb->id,
+		fb->width, fb->height,
+		drm_get_format_name(fb->format->format, &format_name));
+
+	/* queue job before anything that can trigger completion irq */
+	drm_writeback_queue_job(wb_conn, job);
+	conn_state->writeback_job = NULL;
+
+	mdp_irq_register(&mdp5_kms->base, &mdp5_wb->wb_done);
+
+	if (MDP_FORMAT_IS_YUV(fmt)) {
+		wb_csc_setup(mdp5_kms, mdp5_wb->id,
+			mdp_get_default_csc_cfg(CSC_RGB2YUV));
+
+		opmode |= MDP5_WB_DST_OP_MODE_CSC_EN |
+			MDP5_WB_DST_OP_MODE_CSC_SRC_DATA_FORMAT(DATA_FORMAT_RGB) |
+			MDP5_WB_DST_OP_MODE_CSC_DST_DATA_FORMAT(DATA_FORMAT_YUV);
+
+		switch (fmt->chroma_sample) {
+		case CHROMA_420:
+		case CHROMA_H2V1:
+			opmode |= MDP5_WB_DST_OP_MODE_CHROMA_DWN_SAMPLE_EN;
+			break;
+		case CHROMA_H1V2:
+		default:
+			WARN(1, "unsupported wb chroma samp=%d\n",
+				fmt->chroma_sample);
+			return;
+		}
+	}
+
+	dst_format = MDP5_WB_DST_FORMAT_DST_CHROMA_SAMP(fmt->chroma_sample) |
+		MDP5_WB_DST_FORMAT_WRITE_PLANES(fmt->fetch_type) |
+		MDP5_WB_DST_FORMAT_DSTC3_OUT(fmt->bpc_a) |
+		MDP5_WB_DST_FORMAT_DSTC2_OUT(fmt->bpc_r) |
+		MDP5_WB_DST_FORMAT_DSTC1_OUT(fmt->bpc_b) |
+		MDP5_WB_DST_FORMAT_DSTC0_OUT(fmt->bpc_g) |
+		COND(fmt->unpack_tight, MDP5_WB_DST_FORMAT_PACK_TIGHT) |
+		MDP5_WB_DST_FORMAT_PACK_COUNT(fmt->unpack_count - 1) |
+		MDP5_WB_DST_FORMAT_DST_BPP(fmt->cpp - 1);
+
+	if (fmt->bpc_a || fmt->alpha_enable) {
+		dst_format |= MDP5_WB_DST_FORMAT_DSTC3_EN;
+		if (!fmt->alpha_enable)
+			dst_format |= MDP5_WB_DST_FORMAT_DST_ALPHA_X;
+	}
+
+	pattern = MDP5_WB_DST_PACK_PATTERN_ELEMENT3(fmt->unpack[3]) |
+		MDP5_WB_DST_PACK_PATTERN_ELEMENT2(fmt->unpack[2]) |
+		MDP5_WB_DST_PACK_PATTERN_ELEMENT1(fmt->unpack[1]) |
+		MDP5_WB_DST_PACK_PATTERN_ELEMENT0(fmt->unpack[0]);
+
+	ystride0 = MDP5_WB_DST_YSTRIDE0_DST0_YSTRIDE(fb->pitches[0]) |
+		MDP5_WB_DST_YSTRIDE0_DST1_YSTRIDE(fb->pitches[1]);
+	ystride1 = MDP5_WB_DST_YSTRIDE1_DST2_YSTRIDE(fb->pitches[2]) |
+		MDP5_WB_DST_YSTRIDE1_DST3_YSTRIDE(fb->pitches[3]);
+
+	/* get the output resolution from WB device */
+	outsize = MDP5_WB_OUT_SIZE_DST_H(fb->height) |
+		MDP5_WB_OUT_SIZE_DST_W(fb->width);
+
+	mdp5_write(mdp5_kms, REG_MDP5_WB_ALPHA_X_VALUE(mdp5_wb->id), 0xff);
+	mdp5_write(mdp5_kms, REG_MDP5_WB_DST_FORMAT(mdp5_wb->id), dst_format);
+	mdp5_write(mdp5_kms, REG_MDP5_WB_DST_OP_MODE(mdp5_wb->id), opmode);
+	mdp5_write(mdp5_kms, REG_MDP5_WB_DST_PACK_PATTERN(mdp5_wb->id), pattern);
+	mdp5_write(mdp5_kms, REG_MDP5_WB_DST_YSTRIDE0(mdp5_wb->id), ystride0);
+	mdp5_write(mdp5_kms, REG_MDP5_WB_DST_YSTRIDE1(mdp5_wb->id), ystride1);
+	mdp5_write(mdp5_kms, REG_MDP5_WB_OUT_SIZE(mdp5_wb->id), outsize);
+
+	mdp5_crtc_set_pipeline(wb_conn->encoder.crtc);
+
+	mdp5_write(mdp5_kms, REG_MDP5_WB_DST0_ADDR(mdp5_wb->id),
+		msm_framebuffer_iova(fb, priv->kms->aspace, 0));
+	mdp5_write(mdp5_kms, REG_MDP5_WB_DST1_ADDR(mdp5_wb->id),
+		msm_framebuffer_iova(fb, priv->kms->aspace, 1));
+	mdp5_write(mdp5_kms, REG_MDP5_WB_DST2_ADDR(mdp5_wb->id),
+		msm_framebuffer_iova(fb, priv->kms->aspace, 2));
+	mdp5_write(mdp5_kms, REG_MDP5_WB_DST3_ADDR(mdp5_wb->id),
+		msm_framebuffer_iova(fb, priv->kms->aspace, 3));
+
+	/* Notify ctl that wb buffer is ready to trigger start */
+	mdp5_ctl_commit(mdp5_wb->ctl, &mdp5_crtc_state->pipeline,
+		MDP5_CTL_FLUSH_WB, true);
+
+	mdp5_ctl_set_encoder_state(mdp5_wb->ctl,
+		&mdp5_crtc_state->pipeline, true);
+}
+
+static void mdp5_wb_done_irq(struct mdp_irq *irq, uint32_t irqstatus)
+{
+	struct mdp5_wb_connector *mdp5_wb =
+		container_of(irq, struct mdp5_wb_connector, wb_done);
+	struct mdp5_crtc_state *mdp5_crtc_state =
+		to_mdp5_crtc_state(mdp5_wb->base.encoder.crtc->state);
+	struct msm_drm_private *priv = mdp5_wb->base.base.dev->dev_private;
+
+	mdp_irq_unregister(to_mdp_kms(priv->kms), &mdp5_wb->wb_done);
+
+	mdp5_ctl_set_encoder_state(mdp5_wb->ctl,
+		&mdp5_crtc_state->pipeline, false);
+
+	drm_writeback_signal_completion(&mdp5_wb->base, 0);
+}
+
+static const struct drm_encoder_helper_funcs mdp5_wb_encoder_helper_funcs = {
+	.atomic_check = mdp5_wb_encoder_atomic_check,
+};
+
+struct drm_writeback_connector *
+mdp5_wb_connector_init(struct drm_device *dev, struct mdp5_ctl *ctl,
+		unsigned wb_id)
+{
+	struct drm_connector *connector = NULL;
+	struct mdp5_wb_connector *mdp5_wb;
+
+	mdp5_wb = kzalloc(sizeof(*mdp5_wb), GFP_KERNEL);
+	if (!mdp5_wb)
+		return ERR_PTR(-ENOMEM);
+
+	mdp5_wb->id = wb_id;
+	mdp5_wb->ctl = ctl;
+
+	/* construct a dummy intf for WB: */
+// TODO un-inline this (and also in interface_init())
+	mdp5_wb->intf = kzalloc(sizeof(*mdp5_wb->intf), GFP_KERNEL);
+	mdp5_wb->intf->num = -1;
+	mdp5_wb->intf->type = INTF_WB;
+	mdp5_wb->intf->mode = MDP5_INTF_WB_MODE_LINE;
+	mdp5_wb->intf->idx = -1;
+
+	mdp5_wb->wb_done.irq = mdp5_wb_done_irq;
+// TODO just register for all wb irq's until I figure out the mapping..
+	mdp5_wb->wb_done.irqmask = MDP5_IRQ_WB_0_DONE | MDP5_IRQ_WB_1_DONE | MDP5_IRQ_WB_2_DONE;
+
+	connector = &mdp5_wb->base.base;
+
+	drm_connector_helper_add(connector, &mdp5_wb_connector_helper_funcs);
+
+	mdp5_wb->nformats = mdp_get_formats(mdp5_wb->formats,
+		ARRAY_SIZE(mdp5_wb->formats), false);
+
+	drm_writeback_connector_init(dev,
+		&mdp5_wb->base,
+		&mdp5_wb_connector_funcs,
+		&mdp5_wb_encoder_helper_funcs,
+		mdp5_wb->formats,
+		mdp5_wb->nformats);
+
+	connector->interlace_allowed = 0;
+	connector->doublescan_allowed = 0;
+
+	return &mdp5_wb->base;
+}
diff --git a/drivers/gpu/drm/msm/dsi/dsi_host.c b/drivers/gpu/drm/msm/dsi/dsi_host.c
index 7a03a9489708..422f524f7562 100644
--- a/drivers/gpu/drm/msm/dsi/dsi_host.c
+++ b/drivers/gpu/drm/msm/dsi/dsi_host.c
@@ -741,7 +741,7 @@ static void dsi_intr_ctrl(struct msm_dsi_host *msm_host, u32 mask, int enable)
 	else
 		intr &= ~mask;
 
-	DBG("intr=%x enable=%d", intr, enable);
+	VERB("intr=%x enable=%d", intr, enable);
 
 	dsi_write(msm_host, REG_DSI_INTR_CTRL, intr);
 	spin_unlock_irqrestore(&msm_host->intr_lock, flags);
@@ -1465,7 +1465,7 @@ static irqreturn_t dsi_host_irq(int irq, void *ptr)
 	dsi_write(msm_host, REG_DSI_INTR_CTRL, isr);
 	spin_unlock_irqrestore(&msm_host->intr_lock, flags);
 
-	DBG("isr=0x%x, id=%d", isr, msm_host->id);
+	VERB("isr=0x%x, id=%d", isr, msm_host->id);
 
 	if (isr & DSI_IRQ_ERROR)
 		dsi_error(msm_host);
-- 
2.14.3

_______________________________________________
Freedreno mailing list
Freedreno@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/freedreno

^ permalink raw reply related	[flat|nested] 41+ messages in thread

* [RFC 4/4] drm/msm/mdp5: writeback support
@ 2018-02-23 13:17     ` Rob Clark
  0 siblings, 0 replies; 41+ messages in thread
From: Rob Clark @ 2018-02-23 13:17 UTC (permalink / raw)
  To: dri-devel
  Cc: freedreno, linux-arm-msm, Brian Starkey, Liviu Dudau, Rob Clark,
	David Airlie, Archit Taneja, Daniel Vetter, Laurent Pinchart,
	Ville Syrjälä,
	Neil Armstrong, Sean Paul, Sushmita Susheelendra, linux-kernel

In a way, based on the original writeback patch from Jilai Wang, but a
lot has shifted around since then.

Signed-off-by: Rob Clark <robdclark@gmail.com>
---
 drivers/gpu/drm/msm/Makefile              |   1 +
 drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c |  23 +-
 drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c  |  38 +++-
 drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.h  |   7 +-
 drivers/gpu/drm/msm/disp/mdp5/mdp5_wb.c   | 367 ++++++++++++++++++++++++++++++
 drivers/gpu/drm/msm/dsi/dsi_host.c        |   4 +-
 6 files changed, 431 insertions(+), 9 deletions(-)
 create mode 100644 drivers/gpu/drm/msm/disp/mdp5/mdp5_wb.c

diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
index cd40c050b2d7..c9f50adef2db 100644
--- a/drivers/gpu/drm/msm/Makefile
+++ b/drivers/gpu/drm/msm/Makefile
@@ -45,6 +45,7 @@ msm-y := \
 	disp/mdp5/mdp5_mixer.o \
 	disp/mdp5/mdp5_plane.o \
 	disp/mdp5/mdp5_smp.o \
+	disp/mdp5/mdp5_wb.o \
 	msm_atomic.o \
 	msm_debugfs.o \
 	msm_drv.o \
diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c
index 9893e43ba6c5..b00ca88b741d 100644
--- a/drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c
+++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c
@@ -484,7 +484,11 @@ static void mdp5_crtc_atomic_enable(struct drm_crtc *crtc,
 	}
 
 	/* Restore vblank irq handling after power is enabled */
-	drm_crtc_vblank_on(crtc);
+// TODO we can't ->get_scanout_pos() for wb (since virtual intf)..
+// perhaps drm core should be clever enough not to drm_reset_vblank_timestamp()
+// for virtual encoders / writeback?
+	if (mdp5_cstate->pipeline.intf->type != INTF_WB)
+		drm_crtc_vblank_on(crtc);
 
 	mdp5_crtc_mode_set_nofb(crtc);
 
@@ -518,7 +522,11 @@ int mdp5_crtc_setup_pipeline(struct drm_crtc *crtc,
 		u32 caps;
 		int ret;
 
-		caps = MDP_LM_CAP_DISPLAY;
+		if (pipeline->intf->type == INTF_WB)
+			caps = MDP_LM_CAP_WB;
+		else
+			caps = MDP_LM_CAP_DISPLAY;
+
 		if (need_right_mixer)
 			caps |= MDP_LM_CAP_PAIR;
 
@@ -545,6 +553,7 @@ int mdp5_crtc_setup_pipeline(struct drm_crtc *crtc,
 	mdp5_cstate->err_irqmask = intf2err(intf->num);
 	mdp5_cstate->vblank_irqmask = intf2vblank(pipeline->mixer, intf);
 
+// XXX should we be treating WB as cmd_mode??
 	if ((intf->type == INTF_DSI) &&
 	    (intf->mode == MDP5_INTF_DSI_MODE_COMMAND)) {
 		mdp5_cstate->pp_done_irqmask = lm2ppdone(pipeline->mixer);
@@ -639,8 +648,12 @@ static int mdp5_crtc_atomic_check(struct drm_crtc *crtc,
 	}
 
 	/* bail out early if there aren't any planes */
-	if (!cnt)
-		return 0;
+	if (!cnt) {
+		if (!state->active)
+			return 0;
+		dev_err(dev->dev, "%s has no planes!\n", crtc->name);
+		return -EINVAL;
+	}
 
 	hw_cfg = mdp5_cfg_get_hw_config(mdp5_kms->cfg);
 
@@ -1160,7 +1173,7 @@ void mdp5_crtc_wait_for_commit_done(struct drm_crtc *crtc)
 
 	if (mdp5_cstate->cmd_mode)
 		mdp5_crtc_wait_for_pp_done(crtc);
-	else
+	else if (mdp5_cstate->pipeline.intf->type != INTF_WB)
 		mdp5_crtc_wait_for_flush_done(crtc);
 }
 
diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c
index 1f44d8f15ce1..239010905637 100644
--- a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c
+++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c
@@ -427,7 +427,8 @@ static int modeset_init(struct mdp5_kms *mdp5_kms)
 	 * the MDP5 interfaces) than the number of layer mixers present in HW,
 	 * but let's be safe here anyway
 	 */
-	num_crtcs = min(priv->num_encoders, mdp5_kms->num_hwmixers);
+	num_crtcs = min(priv->num_encoders + hw_cfg->wb.count,
+			mdp5_kms->num_hwmixers);
 
 	/*
 	 * Construct planes equaling the number of hw pipes, and CRTCs for the
@@ -482,6 +483,33 @@ static int modeset_init(struct mdp5_kms *mdp5_kms)
 		encoder->possible_crtcs = (1 << priv->num_crtcs) - 1;
 	}
 
+	/*
+	 * Lastly, construct writeback connectors.
+	 */
+	for (i = 0; i < hw_cfg->wb.count; i++) {
+		struct drm_writeback_connector *wb_conn;
+		struct mdp5_ctl *ctl;
+
+		ctl = mdp5_ctlm_request(mdp5_kms->ctlm, -1);
+		if (!ctl) {
+			dev_err(dev->dev,
+				"failed to allocate ctl for writeback %d\n", i);
+			continue;
+		}
+
+		wb_conn = mdp5_wb_connector_init(dev, ctl,
+				hw_cfg->wb.instances[i].id);
+		if (IS_ERR(wb_conn)) {
+			ret = PTR_ERR(wb_conn);
+			dev_err(dev->dev,
+				"failed to construct writeback connector %d (%d)\n",
+				i, ret);
+			goto fail;
+		}
+
+		wb_conn->encoder.possible_crtcs = (1 << priv->num_crtcs) - 1;
+	}
+
 	return 0;
 
 fail:
@@ -555,6 +583,10 @@ static bool mdp5_get_scanoutpos(struct drm_device *dev, unsigned int pipe,
 		return false;
 	}
 
+	/* unsupported for writeback: */
+	if (encoder->encoder_type == DRM_MODE_ENCODER_VIRTUAL)
+		return false;
+
 	vsw = mode->crtc_vsync_end - mode->crtc_vsync_start;
 	vbp = mode->crtc_vtotal - mode->crtc_vsync_end;
 
@@ -610,6 +642,10 @@ static u32 mdp5_get_vblank_counter(struct drm_device *dev, unsigned int pipe)
 	if (!encoder)
 		return 0;
 
+	/* unsupported for writeback: */
+	if (encoder->encoder_type == DRM_MODE_ENCODER_VIRTUAL)
+		return 0;
+
 	return mdp5_encoder_get_framecount(encoder);
 }
 
diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.h b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.h
index 425a03d213e5..be0f93ef33e1 100644
--- a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.h
+++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.h
@@ -18,6 +18,8 @@
 #ifndef __MDP5_KMS_H__
 #define __MDP5_KMS_H__
 
+#include <drm/drm_writeback.h>
+
 #include "msm_drv.h"
 #include "msm_kms.h"
 #include "disp/mdp_kms.h"
@@ -251,7 +253,7 @@ static inline uint32_t intf2vblank(struct mdp5_hw_mixer *mixer,
 		return MDP5_IRQ_PING_PONG_0_RD_PTR << mixer->pp;
 
 	if (intf->type == INTF_WB)
-		return MDP5_IRQ_WB_2_DONE;
+		return MDP5_IRQ_WB_2_DONE | MDP5_IRQ_WB_0_DONE | MDP5_IRQ_WB_1_DONE;
 
 	switch (intf->num) {
 	case 0:  return MDP5_IRQ_INTF0_VSYNC;
@@ -330,4 +332,7 @@ static inline int mdp5_cmd_encoder_set_split_display(
 }
 #endif
 
+struct drm_writeback_connector *mdp5_wb_connector_init(struct drm_device *dev,
+		struct mdp5_ctl *ctl, unsigned wb_id);
+
 #endif /* __MDP5_KMS_H__ */
diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_wb.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_wb.c
new file mode 100644
index 000000000000..3dabd0a1aa8b
--- /dev/null
+++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_wb.c
@@ -0,0 +1,367 @@
+/*
+ * Copyright (C) 2018 Red Hat
+ * Author: Rob Clark <robdclark@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "mdp5_kms.h"
+
+/*
+ * Writeback connector/encoder implementation:
+ */
+
+struct mdp5_wb_connector {
+	struct drm_writeback_connector base;
+
+	u32 nformats;
+	u32 formats[32];
+
+	unsigned id;
+	struct mdp5_ctl *ctl;
+	struct mdp5_interface *intf;
+
+	struct mdp_irq wb_done;
+};
+#define to_mdp5_wb_connector(x) container_of(x, struct mdp5_wb_connector, base)
+
+
+static void mdp5_wb_connector_atomic_commit(struct drm_connector *connector,
+		struct drm_writeback_job *job);
+
+static int mdp5_wb_connector_get_modes(struct drm_connector *connector)
+{
+	struct drm_device *dev = connector->dev;
+
+	return drm_add_modes_noedid(connector, dev->mode_config.max_width,
+			dev->mode_config.max_height);
+}
+
+static enum drm_mode_status
+mdp5_wb_connector_mode_valid(struct drm_connector *connector,
+		struct drm_display_mode *mode)
+{
+	struct drm_device *dev = connector->dev;
+	struct drm_mode_config *mode_config = &dev->mode_config;
+	int w = mode->hdisplay, h = mode->vdisplay;
+
+	if ((w < mode_config->min_width) || (w > mode_config->max_width))
+		return MODE_BAD_HVALUE;
+
+	if ((h < mode_config->min_height) || (h > mode_config->max_height))
+		return MODE_BAD_VVALUE;
+
+	return MODE_OK;
+}
+
+const struct drm_connector_helper_funcs mdp5_wb_connector_helper_funcs = {
+	.get_modes = mdp5_wb_connector_get_modes,
+	.mode_valid = mdp5_wb_connector_mode_valid,
+	.atomic_commit = mdp5_wb_connector_atomic_commit,
+};
+
+static enum drm_connector_status
+mdp5_wb_connector_detect(struct drm_connector *connector, bool force)
+{
+	return connector_status_disconnected;
+}
+
+static void mdp5_wb_connector_destroy(struct drm_connector *connector)
+{
+	drm_connector_cleanup(connector);
+}
+
+static const struct drm_connector_funcs mdp5_wb_connector_funcs = {
+	.reset = drm_atomic_helper_connector_reset,
+	.detect = mdp5_wb_connector_detect,
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.destroy = mdp5_wb_connector_destroy,
+	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static int
+mdp5_wb_encoder_atomic_check(struct drm_encoder *encoder,
+		struct drm_crtc_state *crtc_state,
+		struct drm_connector_state *conn_state)
+{
+	struct msm_drm_private *priv = encoder->dev->dev_private;
+	struct mdp5_crtc_state *mdp5_cstate = to_mdp5_crtc_state(crtc_state);
+	struct mdp5_wb_connector *mdp5_wb = to_mdp5_wb_connector(
+		to_wb_connector(conn_state->connector));
+	struct drm_framebuffer *fb;
+	const struct msm_format *format;
+	const struct mdp_format *mdp_fmt;
+	struct drm_format_name_buf format_name;
+	int ret;
+
+	if (!conn_state->writeback_job || !conn_state->writeback_job->fb)
+		return 0;
+
+	fb = conn_state->writeback_job->fb;
+
+	DBG("wb[%u]: check writeback %ux%u@%s", mdp5_wb->id,
+		fb->width, fb->height,
+		drm_get_format_name(fb->format->format, &format_name));
+
+	format = mdp_get_format(priv->kms, fb->format->format);
+	if (!format) {
+		DBG("Invalid pixel format!");
+		return -EINVAL;
+	}
+
+	mdp_fmt = to_mdp_format(format);
+	if (MDP_FORMAT_IS_YUV(mdp_fmt)) {
+		switch (mdp_fmt->chroma_sample) {
+		case CHROMA_420:
+		case CHROMA_H2V1:
+			/* supported */
+			break;
+		case CHROMA_H1V2:
+		default:
+			DBG("unsupported wb chroma samp=%d\n",
+				mdp_fmt->chroma_sample);
+			return -EINVAL;
+		}
+	}
+
+	/* TODO I think we would prefer to have proper prepare_fb()/cleanup_fb()
+	 * vfuncs, as with plane..  Also, where to unprepare?
+	 */
+	ret = msm_framebuffer_prepare(fb, priv->kms->aspace);
+	if (ret)
+		return ret;
+
+	mdp5_cstate->ctl = mdp5_wb->ctl;
+	mdp5_cstate->pipeline.intf = mdp5_wb->intf;
+	mdp5_cstate->defer_start = true;
+
+	return 0;
+}
+
+static void
+wb_csc_setup(struct mdp5_kms *mdp5_kms, u32 wb_id, struct csc_cfg *csc)
+{
+	uint32_t  i;
+	uint32_t *matrix;
+
+	if (unlikely(!csc))
+		return;
+
+	matrix = csc->matrix;
+	mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_0(wb_id),
+		MDP5_WB_CSC_MATRIX_COEFF_0_COEFF_11(matrix[0]) |
+		MDP5_WB_CSC_MATRIX_COEFF_0_COEFF_12(matrix[1]));
+	mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_1(wb_id),
+		MDP5_WB_CSC_MATRIX_COEFF_1_COEFF_13(matrix[2]) |
+		MDP5_WB_CSC_MATRIX_COEFF_1_COEFF_21(matrix[3]));
+	mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_2(wb_id),
+		MDP5_WB_CSC_MATRIX_COEFF_2_COEFF_22(matrix[4]) |
+		MDP5_WB_CSC_MATRIX_COEFF_2_COEFF_23(matrix[5]));
+	mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_3(wb_id),
+		MDP5_WB_CSC_MATRIX_COEFF_3_COEFF_31(matrix[6]) |
+		MDP5_WB_CSC_MATRIX_COEFF_3_COEFF_32(matrix[7]));
+	mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_4(wb_id),
+		MDP5_WB_CSC_MATRIX_COEFF_4_COEFF_33(matrix[8]));
+
+	for (i = 0; i < ARRAY_SIZE(csc->pre_bias); i++) {
+		uint32_t *pre_clamp = csc->pre_clamp;
+		uint32_t *post_clamp = csc->post_clamp;
+
+		mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_COMP_PRECLAMP(wb_id, i),
+			MDP5_WB_CSC_COMP_PRECLAMP_REG_HIGH(pre_clamp[2*i+1]) |
+			MDP5_WB_CSC_COMP_PRECLAMP_REG_LOW(pre_clamp[2*i]));
+
+		mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_COMP_POSTCLAMP(wb_id, i),
+			MDP5_WB_CSC_COMP_POSTCLAMP_REG_HIGH(post_clamp[2*i+1]) |
+			MDP5_WB_CSC_COMP_POSTCLAMP_REG_LOW(post_clamp[2*i]));
+
+		mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_COMP_PREBIAS(wb_id, i),
+			MDP5_WB_CSC_COMP_PREBIAS_REG_VALUE(csc->pre_bias[i]));
+
+		mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_COMP_POSTBIAS(wb_id, i),
+			MDP5_WB_CSC_COMP_POSTBIAS_REG_VALUE(csc->post_bias[i]));
+	}
+}
+
+static void
+mdp5_wb_connector_atomic_commit(struct drm_connector *connector,
+		struct drm_writeback_job *job)
+{
+	struct msm_drm_private *priv = connector->dev->dev_private;
+	struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(priv->kms));
+	struct drm_connector_state *conn_state = connector->state;
+	struct drm_writeback_connector *wb_conn = to_wb_connector(connector);
+	struct mdp5_crtc_state *mdp5_crtc_state =
+		to_mdp5_crtc_state(wb_conn->encoder.crtc->state);
+	struct mdp5_wb_connector *mdp5_wb = to_mdp5_wb_connector(wb_conn);
+	struct drm_framebuffer *fb = job->fb;
+	struct drm_format_name_buf format_name;
+	const struct mdp_format *fmt =
+		to_mdp_format(mdp_get_format(priv->kms, fb->format->format));
+	u32 ystride0, ystride1, outsize;
+	u32 dst_format, pattern, opmode = 0;
+
+	DBG("wb[%u]: kick writeback %ux%u@%s", mdp5_wb->id,
+		fb->width, fb->height,
+		drm_get_format_name(fb->format->format, &format_name));
+
+	/* queue job before anything that can trigger completion irq */
+	drm_writeback_queue_job(wb_conn, job);
+	conn_state->writeback_job = NULL;
+
+	mdp_irq_register(&mdp5_kms->base, &mdp5_wb->wb_done);
+
+	if (MDP_FORMAT_IS_YUV(fmt)) {
+		wb_csc_setup(mdp5_kms, mdp5_wb->id,
+			mdp_get_default_csc_cfg(CSC_RGB2YUV));
+
+		opmode |= MDP5_WB_DST_OP_MODE_CSC_EN |
+			MDP5_WB_DST_OP_MODE_CSC_SRC_DATA_FORMAT(DATA_FORMAT_RGB) |
+			MDP5_WB_DST_OP_MODE_CSC_DST_DATA_FORMAT(DATA_FORMAT_YUV);
+
+		switch (fmt->chroma_sample) {
+		case CHROMA_420:
+		case CHROMA_H2V1:
+			opmode |= MDP5_WB_DST_OP_MODE_CHROMA_DWN_SAMPLE_EN;
+			break;
+		case CHROMA_H1V2:
+		default:
+			WARN(1, "unsupported wb chroma samp=%d\n",
+				fmt->chroma_sample);
+			return;
+		}
+	}
+
+	dst_format = MDP5_WB_DST_FORMAT_DST_CHROMA_SAMP(fmt->chroma_sample) |
+		MDP5_WB_DST_FORMAT_WRITE_PLANES(fmt->fetch_type) |
+		MDP5_WB_DST_FORMAT_DSTC3_OUT(fmt->bpc_a) |
+		MDP5_WB_DST_FORMAT_DSTC2_OUT(fmt->bpc_r) |
+		MDP5_WB_DST_FORMAT_DSTC1_OUT(fmt->bpc_b) |
+		MDP5_WB_DST_FORMAT_DSTC0_OUT(fmt->bpc_g) |
+		COND(fmt->unpack_tight, MDP5_WB_DST_FORMAT_PACK_TIGHT) |
+		MDP5_WB_DST_FORMAT_PACK_COUNT(fmt->unpack_count - 1) |
+		MDP5_WB_DST_FORMAT_DST_BPP(fmt->cpp - 1);
+
+	if (fmt->bpc_a || fmt->alpha_enable) {
+		dst_format |= MDP5_WB_DST_FORMAT_DSTC3_EN;
+		if (!fmt->alpha_enable)
+			dst_format |= MDP5_WB_DST_FORMAT_DST_ALPHA_X;
+	}
+
+	pattern = MDP5_WB_DST_PACK_PATTERN_ELEMENT3(fmt->unpack[3]) |
+		MDP5_WB_DST_PACK_PATTERN_ELEMENT2(fmt->unpack[2]) |
+		MDP5_WB_DST_PACK_PATTERN_ELEMENT1(fmt->unpack[1]) |
+		MDP5_WB_DST_PACK_PATTERN_ELEMENT0(fmt->unpack[0]);
+
+	ystride0 = MDP5_WB_DST_YSTRIDE0_DST0_YSTRIDE(fb->pitches[0]) |
+		MDP5_WB_DST_YSTRIDE0_DST1_YSTRIDE(fb->pitches[1]);
+	ystride1 = MDP5_WB_DST_YSTRIDE1_DST2_YSTRIDE(fb->pitches[2]) |
+		MDP5_WB_DST_YSTRIDE1_DST3_YSTRIDE(fb->pitches[3]);
+
+	/* get the output resolution from WB device */
+	outsize = MDP5_WB_OUT_SIZE_DST_H(fb->height) |
+		MDP5_WB_OUT_SIZE_DST_W(fb->width);
+
+	mdp5_write(mdp5_kms, REG_MDP5_WB_ALPHA_X_VALUE(mdp5_wb->id), 0xff);
+	mdp5_write(mdp5_kms, REG_MDP5_WB_DST_FORMAT(mdp5_wb->id), dst_format);
+	mdp5_write(mdp5_kms, REG_MDP5_WB_DST_OP_MODE(mdp5_wb->id), opmode);
+	mdp5_write(mdp5_kms, REG_MDP5_WB_DST_PACK_PATTERN(mdp5_wb->id), pattern);
+	mdp5_write(mdp5_kms, REG_MDP5_WB_DST_YSTRIDE0(mdp5_wb->id), ystride0);
+	mdp5_write(mdp5_kms, REG_MDP5_WB_DST_YSTRIDE1(mdp5_wb->id), ystride1);
+	mdp5_write(mdp5_kms, REG_MDP5_WB_OUT_SIZE(mdp5_wb->id), outsize);
+
+	mdp5_crtc_set_pipeline(wb_conn->encoder.crtc);
+
+	mdp5_write(mdp5_kms, REG_MDP5_WB_DST0_ADDR(mdp5_wb->id),
+		msm_framebuffer_iova(fb, priv->kms->aspace, 0));
+	mdp5_write(mdp5_kms, REG_MDP5_WB_DST1_ADDR(mdp5_wb->id),
+		msm_framebuffer_iova(fb, priv->kms->aspace, 1));
+	mdp5_write(mdp5_kms, REG_MDP5_WB_DST2_ADDR(mdp5_wb->id),
+		msm_framebuffer_iova(fb, priv->kms->aspace, 2));
+	mdp5_write(mdp5_kms, REG_MDP5_WB_DST3_ADDR(mdp5_wb->id),
+		msm_framebuffer_iova(fb, priv->kms->aspace, 3));
+
+	/* Notify ctl that wb buffer is ready to trigger start */
+	mdp5_ctl_commit(mdp5_wb->ctl, &mdp5_crtc_state->pipeline,
+		MDP5_CTL_FLUSH_WB, true);
+
+	mdp5_ctl_set_encoder_state(mdp5_wb->ctl,
+		&mdp5_crtc_state->pipeline, true);
+}
+
+static void mdp5_wb_done_irq(struct mdp_irq *irq, uint32_t irqstatus)
+{
+	struct mdp5_wb_connector *mdp5_wb =
+		container_of(irq, struct mdp5_wb_connector, wb_done);
+	struct mdp5_crtc_state *mdp5_crtc_state =
+		to_mdp5_crtc_state(mdp5_wb->base.encoder.crtc->state);
+	struct msm_drm_private *priv = mdp5_wb->base.base.dev->dev_private;
+
+	mdp_irq_unregister(to_mdp_kms(priv->kms), &mdp5_wb->wb_done);
+
+	mdp5_ctl_set_encoder_state(mdp5_wb->ctl,
+		&mdp5_crtc_state->pipeline, false);
+
+	drm_writeback_signal_completion(&mdp5_wb->base, 0);
+}
+
+static const struct drm_encoder_helper_funcs mdp5_wb_encoder_helper_funcs = {
+	.atomic_check = mdp5_wb_encoder_atomic_check,
+};
+
+struct drm_writeback_connector *
+mdp5_wb_connector_init(struct drm_device *dev, struct mdp5_ctl *ctl,
+		unsigned wb_id)
+{
+	struct drm_connector *connector = NULL;
+	struct mdp5_wb_connector *mdp5_wb;
+
+	mdp5_wb = kzalloc(sizeof(*mdp5_wb), GFP_KERNEL);
+	if (!mdp5_wb)
+		return ERR_PTR(-ENOMEM);
+
+	mdp5_wb->id = wb_id;
+	mdp5_wb->ctl = ctl;
+
+	/* construct a dummy intf for WB: */
+// TODO un-inline this (and also in interface_init())
+	mdp5_wb->intf = kzalloc(sizeof(*mdp5_wb->intf), GFP_KERNEL);
+	mdp5_wb->intf->num = -1;
+	mdp5_wb->intf->type = INTF_WB;
+	mdp5_wb->intf->mode = MDP5_INTF_WB_MODE_LINE;
+	mdp5_wb->intf->idx = -1;
+
+	mdp5_wb->wb_done.irq = mdp5_wb_done_irq;
+// TODO just register for all wb irq's until I figure out the mapping..
+	mdp5_wb->wb_done.irqmask = MDP5_IRQ_WB_0_DONE | MDP5_IRQ_WB_1_DONE | MDP5_IRQ_WB_2_DONE;
+
+	connector = &mdp5_wb->base.base;
+
+	drm_connector_helper_add(connector, &mdp5_wb_connector_helper_funcs);
+
+	mdp5_wb->nformats = mdp_get_formats(mdp5_wb->formats,
+		ARRAY_SIZE(mdp5_wb->formats), false);
+
+	drm_writeback_connector_init(dev,
+		&mdp5_wb->base,
+		&mdp5_wb_connector_funcs,
+		&mdp5_wb_encoder_helper_funcs,
+		mdp5_wb->formats,
+		mdp5_wb->nformats);
+
+	connector->interlace_allowed = 0;
+	connector->doublescan_allowed = 0;
+
+	return &mdp5_wb->base;
+}
diff --git a/drivers/gpu/drm/msm/dsi/dsi_host.c b/drivers/gpu/drm/msm/dsi/dsi_host.c
index 7a03a9489708..422f524f7562 100644
--- a/drivers/gpu/drm/msm/dsi/dsi_host.c
+++ b/drivers/gpu/drm/msm/dsi/dsi_host.c
@@ -741,7 +741,7 @@ static void dsi_intr_ctrl(struct msm_dsi_host *msm_host, u32 mask, int enable)
 	else
 		intr &= ~mask;
 
-	DBG("intr=%x enable=%d", intr, enable);
+	VERB("intr=%x enable=%d", intr, enable);
 
 	dsi_write(msm_host, REG_DSI_INTR_CTRL, intr);
 	spin_unlock_irqrestore(&msm_host->intr_lock, flags);
@@ -1465,7 +1465,7 @@ static irqreturn_t dsi_host_irq(int irq, void *ptr)
 	dsi_write(msm_host, REG_DSI_INTR_CTRL, isr);
 	spin_unlock_irqrestore(&msm_host->intr_lock, flags);
 
-	DBG("isr=0x%x, id=%d", isr, msm_host->id);
+	VERB("isr=0x%x, id=%d", isr, msm_host->id);
 
 	if (isr & DSI_IRQ_ERROR)
 		dsi_error(msm_host);
-- 
2.14.3

^ permalink raw reply related	[flat|nested] 41+ messages in thread

* Re: [RFC 1/4] drm: Add writeback connector type
  2018-02-23 13:17     ` Rob Clark
@ 2018-02-23 14:00         ` Liviu Dudau
  -1 siblings, 0 replies; 41+ messages in thread
From: Liviu Dudau @ 2018-02-23 14:00 UTC (permalink / raw)
  To: Rob Clark
  Cc: Jonathan Corbet, David Airlie,
	linux-arm-msm-u79uwXL29TY76Z2rM5mHXA,
	linux-doc-u79uwXL29TY76Z2rM5mHXA, Maarten Lankhorst,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, Sean Paul,
	Gustavo Padovan, Mihail Atanassov,
	freedreno-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, Brian Starkey

Hi Rob,

On Fri, Feb 23, 2018 at 08:17:51AM -0500, Rob Clark wrote:
> From: Brian Starkey <brian.starkey@arm.com>
> 
> Writeback connectors represent writeback engines which can write the
> CRTC output to a memory framebuffer. Add a writeback connector type and
> related support functions.
> 
> Drivers should initialize a writeback connector with
> drm_writeback_connector_init() which takes care of setting up all the
> writeback-specific details on top of the normal functionality of
> drm_connector_init().
> 
> Writeback connectors have a WRITEBACK_FB_ID property, used to set the
> output framebuffer, and a WRITEBACK_PIXEL_FORMATS blob used to expose the
> supported writeback formats to userspace.
> 
> When a framebuffer is attached to a writeback connector with the
> WRITEBACK_FB_ID property, it is used only once (for the commit in which
> it was included), and userspace can never read back the value of
> WRITEBACK_FB_ID. WRITEBACK_FB_ID can only be set if the connector is
> attached to a CRTC.

[snip]

> +static bool create_writeback_properties(struct drm_device *dev)
> +{
> +	struct drm_property *prop;
> +
> +	if (!dev->mode_config.writeback_fb_id_property) {
> +		prop = drm_property_create_object(dev, DRM_MODE_PROP_ATOMIC,
> +						  "WRITEBACK_FB_ID",
> +						  DRM_MODE_OBJECT_FB);
> +		if (!prop)
> +			return false;
> +		dev->mode_config.writeback_fb_id_property = prop;
> +	}
> +
> +	if (!dev->mode_config.writeback_pixel_formats_property) {
> +		prop = drm_property_create(dev, DRM_MODE_PROP_BLOB | DRM_MODE_PROP_IMMUTABLE,
> +					   "WRITEBACK_PIXEL_FORMATS", 0);
> +		if (!prop)
> +			return false;
> +		dev->mode_config.writeback_pixel_formats_property = prop;
> +	}
> +
> +	return true;
> +}

based on a buildbot warning and the fact that the next patch starts
returning -ENOMEM inside the above function, I have this variant in my
internal tree that I was preparing to send out once I've got my i-g-t
tests in better shape:

+static int create_writeback_properties(struct drm_device *dev)
+{
+       struct drm_property *prop;
+
+       if (!dev->mode_config.writeback_fb_id_property) {
+               prop = drm_property_create_object(dev, DRM_MODE_PROP_ATOMIC,
+                                                 "WRITEBACK_FB_ID",
+                                                 DRM_MODE_OBJECT_FB);
+               if (!prop)
+                       return -ENOMEM;
+               dev->mode_config.writeback_fb_id_property = prop;
+       }
+
+       if (!dev->mode_config.writeback_pixel_formats_property) {
+               prop = drm_property_create(dev, DRM_MODE_PROP_BLOB | DRM_MODE_PROP_IMMUTABLE,
+                                          "WRITEBACK_PIXEL_FORMATS", 0);
+               if (!prop)
+                       return -ENOMEM;
+               dev->mode_config.writeback_pixel_formats_property = prop;
+       }
+
+       return 0;
+}


Feel free to use this version in the next update if you're going to send
one, otherwise I guess we could be patching it later.

Best regards,
Liviu


> +
> +static const struct drm_encoder_funcs drm_writeback_encoder_funcs = {
> +	.destroy = drm_encoder_cleanup,
> +};
> +
> +/**
> + * drm_writeback_connector_init - Initialize a writeback connector and its properties
> + * @dev: DRM device
> + * @wb_connector: Writeback connector to initialize
> + * @con_funcs: Connector funcs vtable
> + * @enc_helper_funcs: Encoder helper funcs vtable to be used by the internal encoder
> + * @formats: Array of supported pixel formats for the writeback engine
> + * @n_formats: Length of the formats array
> + *
> + * This function creates the writeback-connector-specific properties if they
> + * have not been already created, initializes the connector as
> + * type DRM_MODE_CONNECTOR_WRITEBACK, and correctly initializes the property
> + * values. It will also create an internal encoder associated with the
> + * drm_writeback_connector and set it to use the @enc_helper_funcs vtable for
> + * the encoder helper.
> + *
> + * Drivers should always use this function instead of drm_connector_init() to
> + * set up writeback connectors.
> + *
> + * Returns: 0 on success, or a negative error code
> + */
> +int drm_writeback_connector_init(struct drm_device *dev,
> +				 struct drm_writeback_connector *wb_connector,
> +				 const struct drm_connector_funcs *con_funcs,
> +				 const struct drm_encoder_helper_funcs *enc_helper_funcs,
> +				 const u32 *formats, int n_formats)
> +{
> +	int ret;
> +	struct drm_property_blob *blob;
> +	struct drm_connector *connector = &wb_connector->base;
> +	struct drm_mode_config *config = &dev->mode_config;
> +
> +	if (!create_writeback_properties(dev))
> +		return -EINVAL;
> +
> +	blob = drm_property_create_blob(dev, n_formats * sizeof(*formats),
> +					formats);
> +	if (IS_ERR(blob))
> +		return PTR_ERR(blob);
> +
> +	drm_encoder_helper_add(&wb_connector->encoder, enc_helper_funcs);
> +	ret = drm_encoder_init(dev, &wb_connector->encoder,
> +			       &drm_writeback_encoder_funcs,
> +			       DRM_MODE_ENCODER_VIRTUAL, NULL);
> +	if (ret)
> +		goto fail;
> +
> +	connector->interlace_allowed = 0;
> +
> +	ret = drm_connector_init(dev, connector, con_funcs,
> +				 DRM_MODE_CONNECTOR_WRITEBACK);
> +	if (ret)
> +		goto connector_fail;
> +
> +	ret = drm_mode_connector_attach_encoder(connector,
> +						&wb_connector->encoder);
> +	if (ret)
> +		goto attach_fail;
> +
> +	INIT_LIST_HEAD(&wb_connector->job_queue);
> +	spin_lock_init(&wb_connector->job_lock);
> +
> +	drm_object_attach_property(&connector->base,
> +				   config->writeback_fb_id_property, 0);
> +
> +	drm_object_attach_property(&connector->base,
> +				   config->writeback_pixel_formats_property,
> +				   blob->base.id);
> +	wb_connector->pixel_formats_blob_ptr = blob;
> +
> +	return 0;
> +
> +attach_fail:
> +	drm_connector_cleanup(connector);
> +connector_fail:
> +	drm_encoder_cleanup(&wb_connector->encoder);
> +fail:
> +	drm_property_unreference_blob(blob);
> +	return ret;
> +}
> +EXPORT_SYMBOL(drm_writeback_connector_init);
> +
> +/**
> + * drm_writeback_queue_job - Queue a writeback job for later signalling
> + * @wb_connector: The writeback connector to queue a job on
> + * @job: The job to queue
> + *
> + * This function adds a job to the job_queue for a writeback connector. It
> + * should be considered to take ownership of the writeback job, and so any other
> + * references to the job must be cleared after calling this function.
> + *
> + * Drivers must ensure that for a given writeback connector, jobs are queued in
> + * exactly the same order as they will be completed by the hardware (and
> + * signaled via drm_writeback_signal_completion).
> + *
> + * For every call to drm_writeback_queue_job() there must be exactly one call to
> + * drm_writeback_signal_completion()
> + *
> + * See also: drm_writeback_signal_completion()
> + */
> +void drm_writeback_queue_job(struct drm_writeback_connector *wb_connector,
> +			     struct drm_writeback_job *job)
> +{
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&wb_connector->job_lock, flags);
> +	list_add_tail(&job->list_entry, &wb_connector->job_queue);
> +	spin_unlock_irqrestore(&wb_connector->job_lock, flags);
> +}
> +EXPORT_SYMBOL(drm_writeback_queue_job);
> +
> +/**
> + * drm_writeback_cleanup_job - Cleanup and free a writeback job
> + * @job: The writeback job to free
> + *
> + * Drops any references held by the writeback job, and frees the structure.
> + */
> +void drm_writeback_cleanup_job(struct drm_writeback_job *job)
> +{
> +	if (!job)
> +		return;
> +
> +	if (job->fb)
> +		drm_framebuffer_unreference(job->fb);
> +	kfree(job);
> +}
> +EXPORT_SYMBOL(drm_writeback_cleanup_job);
> +
> +/*
> + * @cleanup_work: deferred cleanup of a writeback job
> + *
> + * The job cannot be cleaned up directly in drm_writeback_signal_completion,
> + * because it may be called in interrupt context. Dropping the framebuffer
> + * reference can sleep, and so the cleanup is deferred to a workqueue.
> + */
> +static void cleanup_work(struct work_struct *work)
> +{
> +	struct drm_writeback_job *job = container_of(work,
> +						     struct drm_writeback_job,
> +						     cleanup_work);
> +	drm_writeback_cleanup_job(job);
> +}
> +
> +/**
> + * drm_writeback_signal_completion - Signal the completion of a writeback job
> + * @wb_connector: The writeback connector whose job is complete
> + *
> + * Drivers should call this to signal the completion of a previously queued
> + * writeback job. It should be called as soon as possible after the hardware
> + * has finished writing, and may be called from interrupt context.
> + * It is the driver's responsibility to ensure that for a given connector, the
> + * hardware completes writeback jobs in the same order as they are queued.
> + *
> + * Unless the driver is holding its own reference to the framebuffer, it must
> + * not be accessed after calling this function.
> + *
> + * See also: drm_writeback_queue_job()
> + */
> +void
> +drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector)
> +{
> +	unsigned long flags;
> +	struct drm_writeback_job *job;
> +
> +	spin_lock_irqsave(&wb_connector->job_lock, flags);
> +	job = list_first_entry_or_null(&wb_connector->job_queue,
> +				       struct drm_writeback_job,
> +				       list_entry);
> +	if (job)
> +		list_del(&job->list_entry);
> +	spin_unlock_irqrestore(&wb_connector->job_lock, flags);
> +
> +	if (WARN_ON(!job))
> +		return;
> +
> +	INIT_WORK(&job->cleanup_work, cleanup_work);
> +	queue_work(system_long_wq, &job->cleanup_work);
> +}
> +EXPORT_SYMBOL(drm_writeback_signal_completion);
> diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
> index cf13842a6dbd..d7b0263cc5cf 100644
> --- a/include/drm/drm_atomic.h
> +++ b/include/drm/drm_atomic.h
> @@ -594,6 +594,9 @@ void drm_atomic_set_fence_for_plane(struct drm_plane_state *plane_state,
>  int __must_check
>  drm_atomic_set_crtc_for_connector(struct drm_connector_state *conn_state,
>  				  struct drm_crtc *crtc);
> +int drm_atomic_set_writeback_fb_for_connector(
> +		struct drm_connector_state *conn_state,
> +		struct drm_framebuffer *fb);
>  int __must_check
>  drm_atomic_add_affected_connectors(struct drm_atomic_state *state,
>  				   struct drm_crtc *crtc);
> diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
> index 758a176e7b57..8701ebcc68b3 100644
> --- a/include/drm/drm_connector.h
> +++ b/include/drm/drm_connector.h
> @@ -425,6 +425,19 @@ struct drm_connector_state {
>  	 * protection. This is most commonly used for HDCP.
>  	 */
>  	unsigned int content_protection;
> +
> +	/**
> +	 * @writeback_job: Writeback job for writeback connectors
> +	 *
> +	 * Holds the framebuffer for a writeback connector. As the writeback
> +	 * completion may be asynchronous to the normal commit cycle, the
> +	 * writeback job lifetime is managed separately from the normal atomic
> +	 * state by this object.
> +	 *
> +	 * See also: drm_writeback_queue_job() and
> +	 * drm_writeback_signal_completion()
> +	 */
> +	struct drm_writeback_job *writeback_job;
>  };
>  
>  /**
> diff --git a/include/drm/drm_mode_config.h b/include/drm/drm_mode_config.h
> index 7569f22ffef6..c012e1148ec0 100644
> --- a/include/drm/drm_mode_config.h
> +++ b/include/drm/drm_mode_config.h
> @@ -779,6 +779,20 @@ struct drm_mode_config {
>  	 */
>  	struct drm_property *panel_orientation_property;
>  
> +	/**
> +	 * @writeback_fb_id_property: Property for writeback connectors, storing
> +	 * the ID of the output framebuffer.
> +	 * See also: drm_writeback_connector_init()
> +	 */
> +	struct drm_property *writeback_fb_id_property;
> +	/**
> +	 * @writeback_pixel_formats_property: Property for writeback connectors,
> +	 * storing an array of the supported pixel formats for the writeback
> +	 * engine (read-only).
> +	 * See also: drm_writeback_connector_init()
> +	 */
> +	struct drm_property *writeback_pixel_formats_property;
> +
>  	/* dumb ioctl parameters */
>  	uint32_t preferred_depth, prefer_shadow;
>  
> diff --git a/include/drm/drm_modeset_helper_vtables.h b/include/drm/drm_modeset_helper_vtables.h
> index 3e76ca805b0f..97d3a810bc85 100644
> --- a/include/drm/drm_modeset_helper_vtables.h
> +++ b/include/drm/drm_modeset_helper_vtables.h
> @@ -974,6 +974,17 @@ struct drm_connector_helper_funcs {
>  	 */
>  	int (*atomic_check)(struct drm_connector *connector,
>  			    struct drm_connector_state *state);
> +
> +	/**
> +	 * @atomic_commit:
> +	 *
> +	 * For write-back connectors, this is the point to commit the write-
> +	 * back job to hw.
> +	 *
> +	 * This callback is used by the atomic modeset helpers.
> +	 */
> +	void (*atomic_commit)(struct drm_connector *connector,
> +			      struct drm_writeback_job *writeback_job);
>  };
>  
>  /**
> diff --git a/include/drm/drm_writeback.h b/include/drm/drm_writeback.h
> new file mode 100644
> index 000000000000..0bb95fd4907d
> --- /dev/null
> +++ b/include/drm/drm_writeback.h
> @@ -0,0 +1,89 @@
> +/*
> + * (C) COPYRIGHT 2016 ARM Limited. All rights reserved.
> + * Author: Brian Starkey <brian.starkey@arm.com>
> + *
> + * This program is free software and is provided to you under the terms of the
> + * GNU General Public License version 2 as published by the Free Software
> + * Foundation, and any use by you of this program is subject to the terms
> + * of such GNU licence.
> + */
> +
> +#ifndef __DRM_WRITEBACK_H__
> +#define __DRM_WRITEBACK_H__
> +#include <drm/drm_connector.h>
> +#include <drm/drm_encoder.h>
> +#include <linux/workqueue.h>
> +
> +struct drm_writeback_connector {
> +	struct drm_connector base;
> +
> +	/**
> +	 * @encoder: Internal encoder used by the connector to fulfill
> +	 * the DRM framework requirements. The users of the
> +	 * @drm_writeback_connector control the behaviour of the @encoder
> +	 * by passing the @enc_funcs parameter to drm_writeback_connector_init()
> +	 * function.
> +	 */
> +	struct drm_encoder encoder;
> +
> +	/**
> +	 * @pixel_formats_blob_ptr:
> +	 *
> +	 * DRM blob property data for the pixel formats list on writeback
> +	 * connectors
> +	 * See also drm_writeback_connector_init()
> +	 */
> +	struct drm_property_blob *pixel_formats_blob_ptr;
> +
> +	/** @job_lock: Protects job_queue */
> +	spinlock_t job_lock;
> +
> +	/**
> +	 * @job_queue:
> +	 *
> +	 * Holds a list of a connector's writeback jobs; the last item is the
> +	 * most recent. The first item may be either waiting for the hardware
> +	 * to begin writing, or currently being written.
> +	 *
> +	 * See also: drm_writeback_queue_job() and
> +	 * drm_writeback_signal_completion()
> +	 */
> +	struct list_head job_queue;
> +};
> +#define to_wb_connector(x) container_of(x, struct drm_writeback_connector, base)
> +
> +struct drm_writeback_job {
> +	/**
> +	 * @cleanup_work:
> +	 *
> +	 * Used to allow drm_writeback_signal_completion to defer dropping the
> +	 * framebuffer reference to a workqueue.
> +	 */
> +	struct work_struct cleanup_work;
> +	/**
> +	 * @list_entry:
> +	 *
> +	 * List item for the connector's @job_queue
> +	 */
> +	struct list_head list_entry;
> +	/**
> +	 * @fb:
> +	 *
> +	 * Framebuffer to be written to by the writeback connector. Do not set
> +	 * directly, use drm_atomic_set_writeback_fb_for_connector()
> +	 */
> +	struct drm_framebuffer *fb;
> +};
> +
> +int drm_writeback_connector_init(struct drm_device *dev,
> +				 struct drm_writeback_connector *wb_connector,
> +				 const struct drm_connector_funcs *con_funcs,
> +				 const struct drm_encoder_helper_funcs *enc_helper_funcs,
> +				 const u32 *formats, int n_formats);
> +
> +void drm_writeback_queue_job(struct drm_writeback_connector *wb_connector,
> +			     struct drm_writeback_job *job);
> +
> +void drm_writeback_cleanup_job(struct drm_writeback_job *job);
> +void drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector);
> +#endif
> diff --git a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h
> index 2c575794fb52..7b47e184e95e 100644
> --- a/include/uapi/drm/drm_mode.h
> +++ b/include/uapi/drm/drm_mode.h
> @@ -338,6 +338,7 @@ enum drm_mode_subconnector {
>  #define DRM_MODE_CONNECTOR_VIRTUAL      15
>  #define DRM_MODE_CONNECTOR_DSI		16
>  #define DRM_MODE_CONNECTOR_DPI		17
> +#define DRM_MODE_CONNECTOR_WRITEBACK	18
>  
>  struct drm_mode_get_connector {
>  
> -- 
> 2.14.3
> 

-- 
====================
| I would like to |
| fix the world,  |
| but they're not |
| giving me the   |
 \ source code!  /
  ---------------
    ¯\_(ツ)_/¯
_______________________________________________
Freedreno mailing list
Freedreno@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/freedreno

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [RFC 1/4] drm: Add writeback connector type
@ 2018-02-23 14:00         ` Liviu Dudau
  0 siblings, 0 replies; 41+ messages in thread
From: Liviu Dudau @ 2018-02-23 14:00 UTC (permalink / raw)
  To: Rob Clark
  Cc: dri-devel, freedreno, linux-arm-msm, Brian Starkey,
	Mihail Atanassov, Gustavo Padovan, Maarten Lankhorst, Sean Paul,
	David Airlie, Jonathan Corbet, linux-doc, linux-kernel

Hi Rob,

On Fri, Feb 23, 2018 at 08:17:51AM -0500, Rob Clark wrote:
> From: Brian Starkey <brian.starkey@arm.com>
> 
> Writeback connectors represent writeback engines which can write the
> CRTC output to a memory framebuffer. Add a writeback connector type and
> related support functions.
> 
> Drivers should initialize a writeback connector with
> drm_writeback_connector_init() which takes care of setting up all the
> writeback-specific details on top of the normal functionality of
> drm_connector_init().
> 
> Writeback connectors have a WRITEBACK_FB_ID property, used to set the
> output framebuffer, and a WRITEBACK_PIXEL_FORMATS blob used to expose the
> supported writeback formats to userspace.
> 
> When a framebuffer is attached to a writeback connector with the
> WRITEBACK_FB_ID property, it is used only once (for the commit in which
> it was included), and userspace can never read back the value of
> WRITEBACK_FB_ID. WRITEBACK_FB_ID can only be set if the connector is
> attached to a CRTC.

[snip]

> +static bool create_writeback_properties(struct drm_device *dev)
> +{
> +	struct drm_property *prop;
> +
> +	if (!dev->mode_config.writeback_fb_id_property) {
> +		prop = drm_property_create_object(dev, DRM_MODE_PROP_ATOMIC,
> +						  "WRITEBACK_FB_ID",
> +						  DRM_MODE_OBJECT_FB);
> +		if (!prop)
> +			return false;
> +		dev->mode_config.writeback_fb_id_property = prop;
> +	}
> +
> +	if (!dev->mode_config.writeback_pixel_formats_property) {
> +		prop = drm_property_create(dev, DRM_MODE_PROP_BLOB | DRM_MODE_PROP_IMMUTABLE,
> +					   "WRITEBACK_PIXEL_FORMATS", 0);
> +		if (!prop)
> +			return false;
> +		dev->mode_config.writeback_pixel_formats_property = prop;
> +	}
> +
> +	return true;
> +}

based on a buildbot warning and the fact that the next patch starts
returning -ENOMEM inside the above function, I have this variant in my
internal tree that I was preparing to send out once I've got my i-g-t
tests in better shape:

+static int create_writeback_properties(struct drm_device *dev)
+{
+       struct drm_property *prop;
+
+       if (!dev->mode_config.writeback_fb_id_property) {
+               prop = drm_property_create_object(dev, DRM_MODE_PROP_ATOMIC,
+                                                 "WRITEBACK_FB_ID",
+                                                 DRM_MODE_OBJECT_FB);
+               if (!prop)
+                       return -ENOMEM;
+               dev->mode_config.writeback_fb_id_property = prop;
+       }
+
+       if (!dev->mode_config.writeback_pixel_formats_property) {
+               prop = drm_property_create(dev, DRM_MODE_PROP_BLOB | DRM_MODE_PROP_IMMUTABLE,
+                                          "WRITEBACK_PIXEL_FORMATS", 0);
+               if (!prop)
+                       return -ENOMEM;
+               dev->mode_config.writeback_pixel_formats_property = prop;
+       }
+
+       return 0;
+}


Feel free to use this version in the next update if you're going to send
one, otherwise I guess we could be patching it later.

Best regards,
Liviu


> +
> +static const struct drm_encoder_funcs drm_writeback_encoder_funcs = {
> +	.destroy = drm_encoder_cleanup,
> +};
> +
> +/**
> + * drm_writeback_connector_init - Initialize a writeback connector and its properties
> + * @dev: DRM device
> + * @wb_connector: Writeback connector to initialize
> + * @con_funcs: Connector funcs vtable
> + * @enc_helper_funcs: Encoder helper funcs vtable to be used by the internal encoder
> + * @formats: Array of supported pixel formats for the writeback engine
> + * @n_formats: Length of the formats array
> + *
> + * This function creates the writeback-connector-specific properties if they
> + * have not been already created, initializes the connector as
> + * type DRM_MODE_CONNECTOR_WRITEBACK, and correctly initializes the property
> + * values. It will also create an internal encoder associated with the
> + * drm_writeback_connector and set it to use the @enc_helper_funcs vtable for
> + * the encoder helper.
> + *
> + * Drivers should always use this function instead of drm_connector_init() to
> + * set up writeback connectors.
> + *
> + * Returns: 0 on success, or a negative error code
> + */
> +int drm_writeback_connector_init(struct drm_device *dev,
> +				 struct drm_writeback_connector *wb_connector,
> +				 const struct drm_connector_funcs *con_funcs,
> +				 const struct drm_encoder_helper_funcs *enc_helper_funcs,
> +				 const u32 *formats, int n_formats)
> +{
> +	int ret;
> +	struct drm_property_blob *blob;
> +	struct drm_connector *connector = &wb_connector->base;
> +	struct drm_mode_config *config = &dev->mode_config;
> +
> +	if (!create_writeback_properties(dev))
> +		return -EINVAL;
> +
> +	blob = drm_property_create_blob(dev, n_formats * sizeof(*formats),
> +					formats);
> +	if (IS_ERR(blob))
> +		return PTR_ERR(blob);
> +
> +	drm_encoder_helper_add(&wb_connector->encoder, enc_helper_funcs);
> +	ret = drm_encoder_init(dev, &wb_connector->encoder,
> +			       &drm_writeback_encoder_funcs,
> +			       DRM_MODE_ENCODER_VIRTUAL, NULL);
> +	if (ret)
> +		goto fail;
> +
> +	connector->interlace_allowed = 0;
> +
> +	ret = drm_connector_init(dev, connector, con_funcs,
> +				 DRM_MODE_CONNECTOR_WRITEBACK);
> +	if (ret)
> +		goto connector_fail;
> +
> +	ret = drm_mode_connector_attach_encoder(connector,
> +						&wb_connector->encoder);
> +	if (ret)
> +		goto attach_fail;
> +
> +	INIT_LIST_HEAD(&wb_connector->job_queue);
> +	spin_lock_init(&wb_connector->job_lock);
> +
> +	drm_object_attach_property(&connector->base,
> +				   config->writeback_fb_id_property, 0);
> +
> +	drm_object_attach_property(&connector->base,
> +				   config->writeback_pixel_formats_property,
> +				   blob->base.id);
> +	wb_connector->pixel_formats_blob_ptr = blob;
> +
> +	return 0;
> +
> +attach_fail:
> +	drm_connector_cleanup(connector);
> +connector_fail:
> +	drm_encoder_cleanup(&wb_connector->encoder);
> +fail:
> +	drm_property_unreference_blob(blob);
> +	return ret;
> +}
> +EXPORT_SYMBOL(drm_writeback_connector_init);
> +
> +/**
> + * drm_writeback_queue_job - Queue a writeback job for later signalling
> + * @wb_connector: The writeback connector to queue a job on
> + * @job: The job to queue
> + *
> + * This function adds a job to the job_queue for a writeback connector. It
> + * should be considered to take ownership of the writeback job, and so any other
> + * references to the job must be cleared after calling this function.
> + *
> + * Drivers must ensure that for a given writeback connector, jobs are queued in
> + * exactly the same order as they will be completed by the hardware (and
> + * signaled via drm_writeback_signal_completion).
> + *
> + * For every call to drm_writeback_queue_job() there must be exactly one call to
> + * drm_writeback_signal_completion()
> + *
> + * See also: drm_writeback_signal_completion()
> + */
> +void drm_writeback_queue_job(struct drm_writeback_connector *wb_connector,
> +			     struct drm_writeback_job *job)
> +{
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&wb_connector->job_lock, flags);
> +	list_add_tail(&job->list_entry, &wb_connector->job_queue);
> +	spin_unlock_irqrestore(&wb_connector->job_lock, flags);
> +}
> +EXPORT_SYMBOL(drm_writeback_queue_job);
> +
> +/**
> + * drm_writeback_cleanup_job - Cleanup and free a writeback job
> + * @job: The writeback job to free
> + *
> + * Drops any references held by the writeback job, and frees the structure.
> + */
> +void drm_writeback_cleanup_job(struct drm_writeback_job *job)
> +{
> +	if (!job)
> +		return;
> +
> +	if (job->fb)
> +		drm_framebuffer_unreference(job->fb);
> +	kfree(job);
> +}
> +EXPORT_SYMBOL(drm_writeback_cleanup_job);
> +
> +/*
> + * @cleanup_work: deferred cleanup of a writeback job
> + *
> + * The job cannot be cleaned up directly in drm_writeback_signal_completion,
> + * because it may be called in interrupt context. Dropping the framebuffer
> + * reference can sleep, and so the cleanup is deferred to a workqueue.
> + */
> +static void cleanup_work(struct work_struct *work)
> +{
> +	struct drm_writeback_job *job = container_of(work,
> +						     struct drm_writeback_job,
> +						     cleanup_work);
> +	drm_writeback_cleanup_job(job);
> +}
> +
> +/**
> + * drm_writeback_signal_completion - Signal the completion of a writeback job
> + * @wb_connector: The writeback connector whose job is complete
> + *
> + * Drivers should call this to signal the completion of a previously queued
> + * writeback job. It should be called as soon as possible after the hardware
> + * has finished writing, and may be called from interrupt context.
> + * It is the driver's responsibility to ensure that for a given connector, the
> + * hardware completes writeback jobs in the same order as they are queued.
> + *
> + * Unless the driver is holding its own reference to the framebuffer, it must
> + * not be accessed after calling this function.
> + *
> + * See also: drm_writeback_queue_job()
> + */
> +void
> +drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector)
> +{
> +	unsigned long flags;
> +	struct drm_writeback_job *job;
> +
> +	spin_lock_irqsave(&wb_connector->job_lock, flags);
> +	job = list_first_entry_or_null(&wb_connector->job_queue,
> +				       struct drm_writeback_job,
> +				       list_entry);
> +	if (job)
> +		list_del(&job->list_entry);
> +	spin_unlock_irqrestore(&wb_connector->job_lock, flags);
> +
> +	if (WARN_ON(!job))
> +		return;
> +
> +	INIT_WORK(&job->cleanup_work, cleanup_work);
> +	queue_work(system_long_wq, &job->cleanup_work);
> +}
> +EXPORT_SYMBOL(drm_writeback_signal_completion);
> diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
> index cf13842a6dbd..d7b0263cc5cf 100644
> --- a/include/drm/drm_atomic.h
> +++ b/include/drm/drm_atomic.h
> @@ -594,6 +594,9 @@ void drm_atomic_set_fence_for_plane(struct drm_plane_state *plane_state,
>  int __must_check
>  drm_atomic_set_crtc_for_connector(struct drm_connector_state *conn_state,
>  				  struct drm_crtc *crtc);
> +int drm_atomic_set_writeback_fb_for_connector(
> +		struct drm_connector_state *conn_state,
> +		struct drm_framebuffer *fb);
>  int __must_check
>  drm_atomic_add_affected_connectors(struct drm_atomic_state *state,
>  				   struct drm_crtc *crtc);
> diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
> index 758a176e7b57..8701ebcc68b3 100644
> --- a/include/drm/drm_connector.h
> +++ b/include/drm/drm_connector.h
> @@ -425,6 +425,19 @@ struct drm_connector_state {
>  	 * protection. This is most commonly used for HDCP.
>  	 */
>  	unsigned int content_protection;
> +
> +	/**
> +	 * @writeback_job: Writeback job for writeback connectors
> +	 *
> +	 * Holds the framebuffer for a writeback connector. As the writeback
> +	 * completion may be asynchronous to the normal commit cycle, the
> +	 * writeback job lifetime is managed separately from the normal atomic
> +	 * state by this object.
> +	 *
> +	 * See also: drm_writeback_queue_job() and
> +	 * drm_writeback_signal_completion()
> +	 */
> +	struct drm_writeback_job *writeback_job;
>  };
>  
>  /**
> diff --git a/include/drm/drm_mode_config.h b/include/drm/drm_mode_config.h
> index 7569f22ffef6..c012e1148ec0 100644
> --- a/include/drm/drm_mode_config.h
> +++ b/include/drm/drm_mode_config.h
> @@ -779,6 +779,20 @@ struct drm_mode_config {
>  	 */
>  	struct drm_property *panel_orientation_property;
>  
> +	/**
> +	 * @writeback_fb_id_property: Property for writeback connectors, storing
> +	 * the ID of the output framebuffer.
> +	 * See also: drm_writeback_connector_init()
> +	 */
> +	struct drm_property *writeback_fb_id_property;
> +	/**
> +	 * @writeback_pixel_formats_property: Property for writeback connectors,
> +	 * storing an array of the supported pixel formats for the writeback
> +	 * engine (read-only).
> +	 * See also: drm_writeback_connector_init()
> +	 */
> +	struct drm_property *writeback_pixel_formats_property;
> +
>  	/* dumb ioctl parameters */
>  	uint32_t preferred_depth, prefer_shadow;
>  
> diff --git a/include/drm/drm_modeset_helper_vtables.h b/include/drm/drm_modeset_helper_vtables.h
> index 3e76ca805b0f..97d3a810bc85 100644
> --- a/include/drm/drm_modeset_helper_vtables.h
> +++ b/include/drm/drm_modeset_helper_vtables.h
> @@ -974,6 +974,17 @@ struct drm_connector_helper_funcs {
>  	 */
>  	int (*atomic_check)(struct drm_connector *connector,
>  			    struct drm_connector_state *state);
> +
> +	/**
> +	 * @atomic_commit:
> +	 *
> +	 * For write-back connectors, this is the point to commit the write-
> +	 * back job to hw.
> +	 *
> +	 * This callback is used by the atomic modeset helpers.
> +	 */
> +	void (*atomic_commit)(struct drm_connector *connector,
> +			      struct drm_writeback_job *writeback_job);
>  };
>  
>  /**
> diff --git a/include/drm/drm_writeback.h b/include/drm/drm_writeback.h
> new file mode 100644
> index 000000000000..0bb95fd4907d
> --- /dev/null
> +++ b/include/drm/drm_writeback.h
> @@ -0,0 +1,89 @@
> +/*
> + * (C) COPYRIGHT 2016 ARM Limited. All rights reserved.
> + * Author: Brian Starkey <brian.starkey@arm.com>
> + *
> + * This program is free software and is provided to you under the terms of the
> + * GNU General Public License version 2 as published by the Free Software
> + * Foundation, and any use by you of this program is subject to the terms
> + * of such GNU licence.
> + */
> +
> +#ifndef __DRM_WRITEBACK_H__
> +#define __DRM_WRITEBACK_H__
> +#include <drm/drm_connector.h>
> +#include <drm/drm_encoder.h>
> +#include <linux/workqueue.h>
> +
> +struct drm_writeback_connector {
> +	struct drm_connector base;
> +
> +	/**
> +	 * @encoder: Internal encoder used by the connector to fulfill
> +	 * the DRM framework requirements. The users of the
> +	 * @drm_writeback_connector control the behaviour of the @encoder
> +	 * by passing the @enc_funcs parameter to drm_writeback_connector_init()
> +	 * function.
> +	 */
> +	struct drm_encoder encoder;
> +
> +	/**
> +	 * @pixel_formats_blob_ptr:
> +	 *
> +	 * DRM blob property data for the pixel formats list on writeback
> +	 * connectors
> +	 * See also drm_writeback_connector_init()
> +	 */
> +	struct drm_property_blob *pixel_formats_blob_ptr;
> +
> +	/** @job_lock: Protects job_queue */
> +	spinlock_t job_lock;
> +
> +	/**
> +	 * @job_queue:
> +	 *
> +	 * Holds a list of a connector's writeback jobs; the last item is the
> +	 * most recent. The first item may be either waiting for the hardware
> +	 * to begin writing, or currently being written.
> +	 *
> +	 * See also: drm_writeback_queue_job() and
> +	 * drm_writeback_signal_completion()
> +	 */
> +	struct list_head job_queue;
> +};
> +#define to_wb_connector(x) container_of(x, struct drm_writeback_connector, base)
> +
> +struct drm_writeback_job {
> +	/**
> +	 * @cleanup_work:
> +	 *
> +	 * Used to allow drm_writeback_signal_completion to defer dropping the
> +	 * framebuffer reference to a workqueue.
> +	 */
> +	struct work_struct cleanup_work;
> +	/**
> +	 * @list_entry:
> +	 *
> +	 * List item for the connector's @job_queue
> +	 */
> +	struct list_head list_entry;
> +	/**
> +	 * @fb:
> +	 *
> +	 * Framebuffer to be written to by the writeback connector. Do not set
> +	 * directly, use drm_atomic_set_writeback_fb_for_connector()
> +	 */
> +	struct drm_framebuffer *fb;
> +};
> +
> +int drm_writeback_connector_init(struct drm_device *dev,
> +				 struct drm_writeback_connector *wb_connector,
> +				 const struct drm_connector_funcs *con_funcs,
> +				 const struct drm_encoder_helper_funcs *enc_helper_funcs,
> +				 const u32 *formats, int n_formats);
> +
> +void drm_writeback_queue_job(struct drm_writeback_connector *wb_connector,
> +			     struct drm_writeback_job *job);
> +
> +void drm_writeback_cleanup_job(struct drm_writeback_job *job);
> +void drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector);
> +#endif
> diff --git a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h
> index 2c575794fb52..7b47e184e95e 100644
> --- a/include/uapi/drm/drm_mode.h
> +++ b/include/uapi/drm/drm_mode.h
> @@ -338,6 +338,7 @@ enum drm_mode_subconnector {
>  #define DRM_MODE_CONNECTOR_VIRTUAL      15
>  #define DRM_MODE_CONNECTOR_DSI		16
>  #define DRM_MODE_CONNECTOR_DPI		17
> +#define DRM_MODE_CONNECTOR_WRITEBACK	18
>  
>  struct drm_mode_get_connector {
>  
> -- 
> 2.14.3
> 

-- 
====================
| I would like to |
| fix the world,  |
| but they're not |
| giving me the   |
 \ source code!  /
  ---------------
    ¯\_(ツ)_/¯

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [RFC 1/4] drm: Add writeback connector type
  2018-02-23 14:00         ` Liviu Dudau
@ 2018-02-23 14:24             ` Rob Clark
  -1 siblings, 0 replies; 41+ messages in thread
From: Rob Clark @ 2018-02-23 14:24 UTC (permalink / raw)
  To: Liviu Dudau
  Cc: Jonathan Corbet, David Airlie, linux-arm-msm,
	linux-doc-u79uwXL29TY76Z2rM5mHXA, Maarten Lankhorst,
	Linux Kernel Mailing List, dri-devel, Sean Paul, Gustavo Padovan,
	Mihail Atanassov, freedreno, Brian Starkey

On Fri, Feb 23, 2018 at 9:00 AM, Liviu Dudau <liviu.dudau@arm.com> wrote:
> Hi Rob,
>
> On Fri, Feb 23, 2018 at 08:17:51AM -0500, Rob Clark wrote:
>> From: Brian Starkey <brian.starkey@arm.com>
>>
>> Writeback connectors represent writeback engines which can write the
>> CRTC output to a memory framebuffer. Add a writeback connector type and
>> related support functions.
>>
>> Drivers should initialize a writeback connector with
>> drm_writeback_connector_init() which takes care of setting up all the
>> writeback-specific details on top of the normal functionality of
>> drm_connector_init().
>>
>> Writeback connectors have a WRITEBACK_FB_ID property, used to set the
>> output framebuffer, and a WRITEBACK_PIXEL_FORMATS blob used to expose the
>> supported writeback formats to userspace.
>>
>> When a framebuffer is attached to a writeback connector with the
>> WRITEBACK_FB_ID property, it is used only once (for the commit in which
>> it was included), and userspace can never read back the value of
>> WRITEBACK_FB_ID. WRITEBACK_FB_ID can only be set if the connector is
>> attached to a CRTC.
>
> [snip]
>
>> +static bool create_writeback_properties(struct drm_device *dev)
>> +{
>> +     struct drm_property *prop;
>> +
>> +     if (!dev->mode_config.writeback_fb_id_property) {
>> +             prop = drm_property_create_object(dev, DRM_MODE_PROP_ATOMIC,
>> +                                               "WRITEBACK_FB_ID",
>> +                                               DRM_MODE_OBJECT_FB);
>> +             if (!prop)
>> +                     return false;
>> +             dev->mode_config.writeback_fb_id_property = prop;
>> +     }
>> +
>> +     if (!dev->mode_config.writeback_pixel_formats_property) {
>> +             prop = drm_property_create(dev, DRM_MODE_PROP_BLOB | DRM_MODE_PROP_IMMUTABLE,
>> +                                        "WRITEBACK_PIXEL_FORMATS", 0);
>> +             if (!prop)
>> +                     return false;
>> +             dev->mode_config.writeback_pixel_formats_property = prop;
>> +     }
>> +
>> +     return true;
>> +}
>
> based on a buildbot warning and the fact that the next patch starts
> returning -ENOMEM inside the above function, I have this variant in my
> internal tree that I was preparing to send out once I've got my i-g-t
> tests in better shape:
>
> +static int create_writeback_properties(struct drm_device *dev)
> +{
> +       struct drm_property *prop;
> +
> +       if (!dev->mode_config.writeback_fb_id_property) {
> +               prop = drm_property_create_object(dev, DRM_MODE_PROP_ATOMIC,
> +                                                 "WRITEBACK_FB_ID",
> +                                                 DRM_MODE_OBJECT_FB);
> +               if (!prop)
> +                       return -ENOMEM;
> +               dev->mode_config.writeback_fb_id_property = prop;
> +       }
> +
> +       if (!dev->mode_config.writeback_pixel_formats_property) {
> +               prop = drm_property_create(dev, DRM_MODE_PROP_BLOB | DRM_MODE_PROP_IMMUTABLE,
> +                                          "WRITEBACK_PIXEL_FORMATS", 0);
> +               if (!prop)
> +                       return -ENOMEM;
> +               dev->mode_config.writeback_pixel_formats_property = prop;
> +       }
> +
> +       return 0;
> +}
>
>
> Feel free to use this version in the next update if you're going to send
> one, otherwise I guess we could be patching it later.
>

hmm, I meant to keep my addition of funcs->atomic_commit() as a
separate fixup patch so it would be easier for you to fold back into
your patchset, but accidentally squashed it at some point and was too
lazy to split it out again.  Sorry about that.

BR,
-R

> Best regards,
> Liviu
>
>
>> +
>> +static const struct drm_encoder_funcs drm_writeback_encoder_funcs = {
>> +     .destroy = drm_encoder_cleanup,
>> +};
>> +
>> +/**
>> + * drm_writeback_connector_init - Initialize a writeback connector and its properties
>> + * @dev: DRM device
>> + * @wb_connector: Writeback connector to initialize
>> + * @con_funcs: Connector funcs vtable
>> + * @enc_helper_funcs: Encoder helper funcs vtable to be used by the internal encoder
>> + * @formats: Array of supported pixel formats for the writeback engine
>> + * @n_formats: Length of the formats array
>> + *
>> + * This function creates the writeback-connector-specific properties if they
>> + * have not been already created, initializes the connector as
>> + * type DRM_MODE_CONNECTOR_WRITEBACK, and correctly initializes the property
>> + * values. It will also create an internal encoder associated with the
>> + * drm_writeback_connector and set it to use the @enc_helper_funcs vtable for
>> + * the encoder helper.
>> + *
>> + * Drivers should always use this function instead of drm_connector_init() to
>> + * set up writeback connectors.
>> + *
>> + * Returns: 0 on success, or a negative error code
>> + */
>> +int drm_writeback_connector_init(struct drm_device *dev,
>> +                              struct drm_writeback_connector *wb_connector,
>> +                              const struct drm_connector_funcs *con_funcs,
>> +                              const struct drm_encoder_helper_funcs *enc_helper_funcs,
>> +                              const u32 *formats, int n_formats)
>> +{
>> +     int ret;
>> +     struct drm_property_blob *blob;
>> +     struct drm_connector *connector = &wb_connector->base;
>> +     struct drm_mode_config *config = &dev->mode_config;
>> +
>> +     if (!create_writeback_properties(dev))
>> +             return -EINVAL;
>> +
>> +     blob = drm_property_create_blob(dev, n_formats * sizeof(*formats),
>> +                                     formats);
>> +     if (IS_ERR(blob))
>> +             return PTR_ERR(blob);
>> +
>> +     drm_encoder_helper_add(&wb_connector->encoder, enc_helper_funcs);
>> +     ret = drm_encoder_init(dev, &wb_connector->encoder,
>> +                            &drm_writeback_encoder_funcs,
>> +                            DRM_MODE_ENCODER_VIRTUAL, NULL);
>> +     if (ret)
>> +             goto fail;
>> +
>> +     connector->interlace_allowed = 0;
>> +
>> +     ret = drm_connector_init(dev, connector, con_funcs,
>> +                              DRM_MODE_CONNECTOR_WRITEBACK);
>> +     if (ret)
>> +             goto connector_fail;
>> +
>> +     ret = drm_mode_connector_attach_encoder(connector,
>> +                                             &wb_connector->encoder);
>> +     if (ret)
>> +             goto attach_fail;
>> +
>> +     INIT_LIST_HEAD(&wb_connector->job_queue);
>> +     spin_lock_init(&wb_connector->job_lock);
>> +
>> +     drm_object_attach_property(&connector->base,
>> +                                config->writeback_fb_id_property, 0);
>> +
>> +     drm_object_attach_property(&connector->base,
>> +                                config->writeback_pixel_formats_property,
>> +                                blob->base.id);
>> +     wb_connector->pixel_formats_blob_ptr = blob;
>> +
>> +     return 0;
>> +
>> +attach_fail:
>> +     drm_connector_cleanup(connector);
>> +connector_fail:
>> +     drm_encoder_cleanup(&wb_connector->encoder);
>> +fail:
>> +     drm_property_unreference_blob(blob);
>> +     return ret;
>> +}
>> +EXPORT_SYMBOL(drm_writeback_connector_init);
>> +
>> +/**
>> + * drm_writeback_queue_job - Queue a writeback job for later signalling
>> + * @wb_connector: The writeback connector to queue a job on
>> + * @job: The job to queue
>> + *
>> + * This function adds a job to the job_queue for a writeback connector. It
>> + * should be considered to take ownership of the writeback job, and so any other
>> + * references to the job must be cleared after calling this function.
>> + *
>> + * Drivers must ensure that for a given writeback connector, jobs are queued in
>> + * exactly the same order as they will be completed by the hardware (and
>> + * signaled via drm_writeback_signal_completion).
>> + *
>> + * For every call to drm_writeback_queue_job() there must be exactly one call to
>> + * drm_writeback_signal_completion()
>> + *
>> + * See also: drm_writeback_signal_completion()
>> + */
>> +void drm_writeback_queue_job(struct drm_writeback_connector *wb_connector,
>> +                          struct drm_writeback_job *job)
>> +{
>> +     unsigned long flags;
>> +
>> +     spin_lock_irqsave(&wb_connector->job_lock, flags);
>> +     list_add_tail(&job->list_entry, &wb_connector->job_queue);
>> +     spin_unlock_irqrestore(&wb_connector->job_lock, flags);
>> +}
>> +EXPORT_SYMBOL(drm_writeback_queue_job);
>> +
>> +/**
>> + * drm_writeback_cleanup_job - Cleanup and free a writeback job
>> + * @job: The writeback job to free
>> + *
>> + * Drops any references held by the writeback job, and frees the structure.
>> + */
>> +void drm_writeback_cleanup_job(struct drm_writeback_job *job)
>> +{
>> +     if (!job)
>> +             return;
>> +
>> +     if (job->fb)
>> +             drm_framebuffer_unreference(job->fb);
>> +     kfree(job);
>> +}
>> +EXPORT_SYMBOL(drm_writeback_cleanup_job);
>> +
>> +/*
>> + * @cleanup_work: deferred cleanup of a writeback job
>> + *
>> + * The job cannot be cleaned up directly in drm_writeback_signal_completion,
>> + * because it may be called in interrupt context. Dropping the framebuffer
>> + * reference can sleep, and so the cleanup is deferred to a workqueue.
>> + */
>> +static void cleanup_work(struct work_struct *work)
>> +{
>> +     struct drm_writeback_job *job = container_of(work,
>> +                                                  struct drm_writeback_job,
>> +                                                  cleanup_work);
>> +     drm_writeback_cleanup_job(job);
>> +}
>> +
>> +/**
>> + * drm_writeback_signal_completion - Signal the completion of a writeback job
>> + * @wb_connector: The writeback connector whose job is complete
>> + *
>> + * Drivers should call this to signal the completion of a previously queued
>> + * writeback job. It should be called as soon as possible after the hardware
>> + * has finished writing, and may be called from interrupt context.
>> + * It is the driver's responsibility to ensure that for a given connector, the
>> + * hardware completes writeback jobs in the same order as they are queued.
>> + *
>> + * Unless the driver is holding its own reference to the framebuffer, it must
>> + * not be accessed after calling this function.
>> + *
>> + * See also: drm_writeback_queue_job()
>> + */
>> +void
>> +drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector)
>> +{
>> +     unsigned long flags;
>> +     struct drm_writeback_job *job;
>> +
>> +     spin_lock_irqsave(&wb_connector->job_lock, flags);
>> +     job = list_first_entry_or_null(&wb_connector->job_queue,
>> +                                    struct drm_writeback_job,
>> +                                    list_entry);
>> +     if (job)
>> +             list_del(&job->list_entry);
>> +     spin_unlock_irqrestore(&wb_connector->job_lock, flags);
>> +
>> +     if (WARN_ON(!job))
>> +             return;
>> +
>> +     INIT_WORK(&job->cleanup_work, cleanup_work);
>> +     queue_work(system_long_wq, &job->cleanup_work);
>> +}
>> +EXPORT_SYMBOL(drm_writeback_signal_completion);
>> diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
>> index cf13842a6dbd..d7b0263cc5cf 100644
>> --- a/include/drm/drm_atomic.h
>> +++ b/include/drm/drm_atomic.h
>> @@ -594,6 +594,9 @@ void drm_atomic_set_fence_for_plane(struct drm_plane_state *plane_state,
>>  int __must_check
>>  drm_atomic_set_crtc_for_connector(struct drm_connector_state *conn_state,
>>                                 struct drm_crtc *crtc);
>> +int drm_atomic_set_writeback_fb_for_connector(
>> +             struct drm_connector_state *conn_state,
>> +             struct drm_framebuffer *fb);
>>  int __must_check
>>  drm_atomic_add_affected_connectors(struct drm_atomic_state *state,
>>                                  struct drm_crtc *crtc);
>> diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
>> index 758a176e7b57..8701ebcc68b3 100644
>> --- a/include/drm/drm_connector.h
>> +++ b/include/drm/drm_connector.h
>> @@ -425,6 +425,19 @@ struct drm_connector_state {
>>        * protection. This is most commonly used for HDCP.
>>        */
>>       unsigned int content_protection;
>> +
>> +     /**
>> +      * @writeback_job: Writeback job for writeback connectors
>> +      *
>> +      * Holds the framebuffer for a writeback connector. As the writeback
>> +      * completion may be asynchronous to the normal commit cycle, the
>> +      * writeback job lifetime is managed separately from the normal atomic
>> +      * state by this object.
>> +      *
>> +      * See also: drm_writeback_queue_job() and
>> +      * drm_writeback_signal_completion()
>> +      */
>> +     struct drm_writeback_job *writeback_job;
>>  };
>>
>>  /**
>> diff --git a/include/drm/drm_mode_config.h b/include/drm/drm_mode_config.h
>> index 7569f22ffef6..c012e1148ec0 100644
>> --- a/include/drm/drm_mode_config.h
>> +++ b/include/drm/drm_mode_config.h
>> @@ -779,6 +779,20 @@ struct drm_mode_config {
>>        */
>>       struct drm_property *panel_orientation_property;
>>
>> +     /**
>> +      * @writeback_fb_id_property: Property for writeback connectors, storing
>> +      * the ID of the output framebuffer.
>> +      * See also: drm_writeback_connector_init()
>> +      */
>> +     struct drm_property *writeback_fb_id_property;
>> +     /**
>> +      * @writeback_pixel_formats_property: Property for writeback connectors,
>> +      * storing an array of the supported pixel formats for the writeback
>> +      * engine (read-only).
>> +      * See also: drm_writeback_connector_init()
>> +      */
>> +     struct drm_property *writeback_pixel_formats_property;
>> +
>>       /* dumb ioctl parameters */
>>       uint32_t preferred_depth, prefer_shadow;
>>
>> diff --git a/include/drm/drm_modeset_helper_vtables.h b/include/drm/drm_modeset_helper_vtables.h
>> index 3e76ca805b0f..97d3a810bc85 100644
>> --- a/include/drm/drm_modeset_helper_vtables.h
>> +++ b/include/drm/drm_modeset_helper_vtables.h
>> @@ -974,6 +974,17 @@ struct drm_connector_helper_funcs {
>>        */
>>       int (*atomic_check)(struct drm_connector *connector,
>>                           struct drm_connector_state *state);
>> +
>> +     /**
>> +      * @atomic_commit:
>> +      *
>> +      * For write-back connectors, this is the point to commit the write-
>> +      * back job to hw.
>> +      *
>> +      * This callback is used by the atomic modeset helpers.
>> +      */
>> +     void (*atomic_commit)(struct drm_connector *connector,
>> +                           struct drm_writeback_job *writeback_job);
>>  };
>>
>>  /**
>> diff --git a/include/drm/drm_writeback.h b/include/drm/drm_writeback.h
>> new file mode 100644
>> index 000000000000..0bb95fd4907d
>> --- /dev/null
>> +++ b/include/drm/drm_writeback.h
>> @@ -0,0 +1,89 @@
>> +/*
>> + * (C) COPYRIGHT 2016 ARM Limited. All rights reserved.
>> + * Author: Brian Starkey <brian.starkey@arm.com>
>> + *
>> + * This program is free software and is provided to you under the terms of the
>> + * GNU General Public License version 2 as published by the Free Software
>> + * Foundation, and any use by you of this program is subject to the terms
>> + * of such GNU licence.
>> + */
>> +
>> +#ifndef __DRM_WRITEBACK_H__
>> +#define __DRM_WRITEBACK_H__
>> +#include <drm/drm_connector.h>
>> +#include <drm/drm_encoder.h>
>> +#include <linux/workqueue.h>
>> +
>> +struct drm_writeback_connector {
>> +     struct drm_connector base;
>> +
>> +     /**
>> +      * @encoder: Internal encoder used by the connector to fulfill
>> +      * the DRM framework requirements. The users of the
>> +      * @drm_writeback_connector control the behaviour of the @encoder
>> +      * by passing the @enc_funcs parameter to drm_writeback_connector_init()
>> +      * function.
>> +      */
>> +     struct drm_encoder encoder;
>> +
>> +     /**
>> +      * @pixel_formats_blob_ptr:
>> +      *
>> +      * DRM blob property data for the pixel formats list on writeback
>> +      * connectors
>> +      * See also drm_writeback_connector_init()
>> +      */
>> +     struct drm_property_blob *pixel_formats_blob_ptr;
>> +
>> +     /** @job_lock: Protects job_queue */
>> +     spinlock_t job_lock;
>> +
>> +     /**
>> +      * @job_queue:
>> +      *
>> +      * Holds a list of a connector's writeback jobs; the last item is the
>> +      * most recent. The first item may be either waiting for the hardware
>> +      * to begin writing, or currently being written.
>> +      *
>> +      * See also: drm_writeback_queue_job() and
>> +      * drm_writeback_signal_completion()
>> +      */
>> +     struct list_head job_queue;
>> +};
>> +#define to_wb_connector(x) container_of(x, struct drm_writeback_connector, base)
>> +
>> +struct drm_writeback_job {
>> +     /**
>> +      * @cleanup_work:
>> +      *
>> +      * Used to allow drm_writeback_signal_completion to defer dropping the
>> +      * framebuffer reference to a workqueue.
>> +      */
>> +     struct work_struct cleanup_work;
>> +     /**
>> +      * @list_entry:
>> +      *
>> +      * List item for the connector's @job_queue
>> +      */
>> +     struct list_head list_entry;
>> +     /**
>> +      * @fb:
>> +      *
>> +      * Framebuffer to be written to by the writeback connector. Do not set
>> +      * directly, use drm_atomic_set_writeback_fb_for_connector()
>> +      */
>> +     struct drm_framebuffer *fb;
>> +};
>> +
>> +int drm_writeback_connector_init(struct drm_device *dev,
>> +                              struct drm_writeback_connector *wb_connector,
>> +                              const struct drm_connector_funcs *con_funcs,
>> +                              const struct drm_encoder_helper_funcs *enc_helper_funcs,
>> +                              const u32 *formats, int n_formats);
>> +
>> +void drm_writeback_queue_job(struct drm_writeback_connector *wb_connector,
>> +                          struct drm_writeback_job *job);
>> +
>> +void drm_writeback_cleanup_job(struct drm_writeback_job *job);
>> +void drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector);
>> +#endif
>> diff --git a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h
>> index 2c575794fb52..7b47e184e95e 100644
>> --- a/include/uapi/drm/drm_mode.h
>> +++ b/include/uapi/drm/drm_mode.h
>> @@ -338,6 +338,7 @@ enum drm_mode_subconnector {
>>  #define DRM_MODE_CONNECTOR_VIRTUAL      15
>>  #define DRM_MODE_CONNECTOR_DSI               16
>>  #define DRM_MODE_CONNECTOR_DPI               17
>> +#define DRM_MODE_CONNECTOR_WRITEBACK 18
>>
>>  struct drm_mode_get_connector {
>>
>> --
>> 2.14.3
>>
>
> --
> ====================
> | I would like to |
> | fix the world,  |
> | but they're not |
> | giving me the   |
>  \ source code!  /
>   ---------------
>     ¯\_(ツ)_/¯
_______________________________________________
Freedreno mailing list
Freedreno@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/freedreno

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [RFC 1/4] drm: Add writeback connector type
@ 2018-02-23 14:24             ` Rob Clark
  0 siblings, 0 replies; 41+ messages in thread
From: Rob Clark @ 2018-02-23 14:24 UTC (permalink / raw)
  To: Liviu Dudau
  Cc: dri-devel, freedreno, linux-arm-msm, Brian Starkey,
	Mihail Atanassov, Gustavo Padovan, Maarten Lankhorst, Sean Paul,
	David Airlie, Jonathan Corbet, linux-doc,
	Linux Kernel Mailing List

On Fri, Feb 23, 2018 at 9:00 AM, Liviu Dudau <liviu.dudau@arm.com> wrote:
> Hi Rob,
>
> On Fri, Feb 23, 2018 at 08:17:51AM -0500, Rob Clark wrote:
>> From: Brian Starkey <brian.starkey@arm.com>
>>
>> Writeback connectors represent writeback engines which can write the
>> CRTC output to a memory framebuffer. Add a writeback connector type and
>> related support functions.
>>
>> Drivers should initialize a writeback connector with
>> drm_writeback_connector_init() which takes care of setting up all the
>> writeback-specific details on top of the normal functionality of
>> drm_connector_init().
>>
>> Writeback connectors have a WRITEBACK_FB_ID property, used to set the
>> output framebuffer, and a WRITEBACK_PIXEL_FORMATS blob used to expose the
>> supported writeback formats to userspace.
>>
>> When a framebuffer is attached to a writeback connector with the
>> WRITEBACK_FB_ID property, it is used only once (for the commit in which
>> it was included), and userspace can never read back the value of
>> WRITEBACK_FB_ID. WRITEBACK_FB_ID can only be set if the connector is
>> attached to a CRTC.
>
> [snip]
>
>> +static bool create_writeback_properties(struct drm_device *dev)
>> +{
>> +     struct drm_property *prop;
>> +
>> +     if (!dev->mode_config.writeback_fb_id_property) {
>> +             prop = drm_property_create_object(dev, DRM_MODE_PROP_ATOMIC,
>> +                                               "WRITEBACK_FB_ID",
>> +                                               DRM_MODE_OBJECT_FB);
>> +             if (!prop)
>> +                     return false;
>> +             dev->mode_config.writeback_fb_id_property = prop;
>> +     }
>> +
>> +     if (!dev->mode_config.writeback_pixel_formats_property) {
>> +             prop = drm_property_create(dev, DRM_MODE_PROP_BLOB | DRM_MODE_PROP_IMMUTABLE,
>> +                                        "WRITEBACK_PIXEL_FORMATS", 0);
>> +             if (!prop)
>> +                     return false;
>> +             dev->mode_config.writeback_pixel_formats_property = prop;
>> +     }
>> +
>> +     return true;
>> +}
>
> based on a buildbot warning and the fact that the next patch starts
> returning -ENOMEM inside the above function, I have this variant in my
> internal tree that I was preparing to send out once I've got my i-g-t
> tests in better shape:
>
> +static int create_writeback_properties(struct drm_device *dev)
> +{
> +       struct drm_property *prop;
> +
> +       if (!dev->mode_config.writeback_fb_id_property) {
> +               prop = drm_property_create_object(dev, DRM_MODE_PROP_ATOMIC,
> +                                                 "WRITEBACK_FB_ID",
> +                                                 DRM_MODE_OBJECT_FB);
> +               if (!prop)
> +                       return -ENOMEM;
> +               dev->mode_config.writeback_fb_id_property = prop;
> +       }
> +
> +       if (!dev->mode_config.writeback_pixel_formats_property) {
> +               prop = drm_property_create(dev, DRM_MODE_PROP_BLOB | DRM_MODE_PROP_IMMUTABLE,
> +                                          "WRITEBACK_PIXEL_FORMATS", 0);
> +               if (!prop)
> +                       return -ENOMEM;
> +               dev->mode_config.writeback_pixel_formats_property = prop;
> +       }
> +
> +       return 0;
> +}
>
>
> Feel free to use this version in the next update if you're going to send
> one, otherwise I guess we could be patching it later.
>

hmm, I meant to keep my addition of funcs->atomic_commit() as a
separate fixup patch so it would be easier for you to fold back into
your patchset, but accidentally squashed it at some point and was too
lazy to split it out again.  Sorry about that.

BR,
-R

> Best regards,
> Liviu
>
>
>> +
>> +static const struct drm_encoder_funcs drm_writeback_encoder_funcs = {
>> +     .destroy = drm_encoder_cleanup,
>> +};
>> +
>> +/**
>> + * drm_writeback_connector_init - Initialize a writeback connector and its properties
>> + * @dev: DRM device
>> + * @wb_connector: Writeback connector to initialize
>> + * @con_funcs: Connector funcs vtable
>> + * @enc_helper_funcs: Encoder helper funcs vtable to be used by the internal encoder
>> + * @formats: Array of supported pixel formats for the writeback engine
>> + * @n_formats: Length of the formats array
>> + *
>> + * This function creates the writeback-connector-specific properties if they
>> + * have not been already created, initializes the connector as
>> + * type DRM_MODE_CONNECTOR_WRITEBACK, and correctly initializes the property
>> + * values. It will also create an internal encoder associated with the
>> + * drm_writeback_connector and set it to use the @enc_helper_funcs vtable for
>> + * the encoder helper.
>> + *
>> + * Drivers should always use this function instead of drm_connector_init() to
>> + * set up writeback connectors.
>> + *
>> + * Returns: 0 on success, or a negative error code
>> + */
>> +int drm_writeback_connector_init(struct drm_device *dev,
>> +                              struct drm_writeback_connector *wb_connector,
>> +                              const struct drm_connector_funcs *con_funcs,
>> +                              const struct drm_encoder_helper_funcs *enc_helper_funcs,
>> +                              const u32 *formats, int n_formats)
>> +{
>> +     int ret;
>> +     struct drm_property_blob *blob;
>> +     struct drm_connector *connector = &wb_connector->base;
>> +     struct drm_mode_config *config = &dev->mode_config;
>> +
>> +     if (!create_writeback_properties(dev))
>> +             return -EINVAL;
>> +
>> +     blob = drm_property_create_blob(dev, n_formats * sizeof(*formats),
>> +                                     formats);
>> +     if (IS_ERR(blob))
>> +             return PTR_ERR(blob);
>> +
>> +     drm_encoder_helper_add(&wb_connector->encoder, enc_helper_funcs);
>> +     ret = drm_encoder_init(dev, &wb_connector->encoder,
>> +                            &drm_writeback_encoder_funcs,
>> +                            DRM_MODE_ENCODER_VIRTUAL, NULL);
>> +     if (ret)
>> +             goto fail;
>> +
>> +     connector->interlace_allowed = 0;
>> +
>> +     ret = drm_connector_init(dev, connector, con_funcs,
>> +                              DRM_MODE_CONNECTOR_WRITEBACK);
>> +     if (ret)
>> +             goto connector_fail;
>> +
>> +     ret = drm_mode_connector_attach_encoder(connector,
>> +                                             &wb_connector->encoder);
>> +     if (ret)
>> +             goto attach_fail;
>> +
>> +     INIT_LIST_HEAD(&wb_connector->job_queue);
>> +     spin_lock_init(&wb_connector->job_lock);
>> +
>> +     drm_object_attach_property(&connector->base,
>> +                                config->writeback_fb_id_property, 0);
>> +
>> +     drm_object_attach_property(&connector->base,
>> +                                config->writeback_pixel_formats_property,
>> +                                blob->base.id);
>> +     wb_connector->pixel_formats_blob_ptr = blob;
>> +
>> +     return 0;
>> +
>> +attach_fail:
>> +     drm_connector_cleanup(connector);
>> +connector_fail:
>> +     drm_encoder_cleanup(&wb_connector->encoder);
>> +fail:
>> +     drm_property_unreference_blob(blob);
>> +     return ret;
>> +}
>> +EXPORT_SYMBOL(drm_writeback_connector_init);
>> +
>> +/**
>> + * drm_writeback_queue_job - Queue a writeback job for later signalling
>> + * @wb_connector: The writeback connector to queue a job on
>> + * @job: The job to queue
>> + *
>> + * This function adds a job to the job_queue for a writeback connector. It
>> + * should be considered to take ownership of the writeback job, and so any other
>> + * references to the job must be cleared after calling this function.
>> + *
>> + * Drivers must ensure that for a given writeback connector, jobs are queued in
>> + * exactly the same order as they will be completed by the hardware (and
>> + * signaled via drm_writeback_signal_completion).
>> + *
>> + * For every call to drm_writeback_queue_job() there must be exactly one call to
>> + * drm_writeback_signal_completion()
>> + *
>> + * See also: drm_writeback_signal_completion()
>> + */
>> +void drm_writeback_queue_job(struct drm_writeback_connector *wb_connector,
>> +                          struct drm_writeback_job *job)
>> +{
>> +     unsigned long flags;
>> +
>> +     spin_lock_irqsave(&wb_connector->job_lock, flags);
>> +     list_add_tail(&job->list_entry, &wb_connector->job_queue);
>> +     spin_unlock_irqrestore(&wb_connector->job_lock, flags);
>> +}
>> +EXPORT_SYMBOL(drm_writeback_queue_job);
>> +
>> +/**
>> + * drm_writeback_cleanup_job - Cleanup and free a writeback job
>> + * @job: The writeback job to free
>> + *
>> + * Drops any references held by the writeback job, and frees the structure.
>> + */
>> +void drm_writeback_cleanup_job(struct drm_writeback_job *job)
>> +{
>> +     if (!job)
>> +             return;
>> +
>> +     if (job->fb)
>> +             drm_framebuffer_unreference(job->fb);
>> +     kfree(job);
>> +}
>> +EXPORT_SYMBOL(drm_writeback_cleanup_job);
>> +
>> +/*
>> + * @cleanup_work: deferred cleanup of a writeback job
>> + *
>> + * The job cannot be cleaned up directly in drm_writeback_signal_completion,
>> + * because it may be called in interrupt context. Dropping the framebuffer
>> + * reference can sleep, and so the cleanup is deferred to a workqueue.
>> + */
>> +static void cleanup_work(struct work_struct *work)
>> +{
>> +     struct drm_writeback_job *job = container_of(work,
>> +                                                  struct drm_writeback_job,
>> +                                                  cleanup_work);
>> +     drm_writeback_cleanup_job(job);
>> +}
>> +
>> +/**
>> + * drm_writeback_signal_completion - Signal the completion of a writeback job
>> + * @wb_connector: The writeback connector whose job is complete
>> + *
>> + * Drivers should call this to signal the completion of a previously queued
>> + * writeback job. It should be called as soon as possible after the hardware
>> + * has finished writing, and may be called from interrupt context.
>> + * It is the driver's responsibility to ensure that for a given connector, the
>> + * hardware completes writeback jobs in the same order as they are queued.
>> + *
>> + * Unless the driver is holding its own reference to the framebuffer, it must
>> + * not be accessed after calling this function.
>> + *
>> + * See also: drm_writeback_queue_job()
>> + */
>> +void
>> +drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector)
>> +{
>> +     unsigned long flags;
>> +     struct drm_writeback_job *job;
>> +
>> +     spin_lock_irqsave(&wb_connector->job_lock, flags);
>> +     job = list_first_entry_or_null(&wb_connector->job_queue,
>> +                                    struct drm_writeback_job,
>> +                                    list_entry);
>> +     if (job)
>> +             list_del(&job->list_entry);
>> +     spin_unlock_irqrestore(&wb_connector->job_lock, flags);
>> +
>> +     if (WARN_ON(!job))
>> +             return;
>> +
>> +     INIT_WORK(&job->cleanup_work, cleanup_work);
>> +     queue_work(system_long_wq, &job->cleanup_work);
>> +}
>> +EXPORT_SYMBOL(drm_writeback_signal_completion);
>> diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
>> index cf13842a6dbd..d7b0263cc5cf 100644
>> --- a/include/drm/drm_atomic.h
>> +++ b/include/drm/drm_atomic.h
>> @@ -594,6 +594,9 @@ void drm_atomic_set_fence_for_plane(struct drm_plane_state *plane_state,
>>  int __must_check
>>  drm_atomic_set_crtc_for_connector(struct drm_connector_state *conn_state,
>>                                 struct drm_crtc *crtc);
>> +int drm_atomic_set_writeback_fb_for_connector(
>> +             struct drm_connector_state *conn_state,
>> +             struct drm_framebuffer *fb);
>>  int __must_check
>>  drm_atomic_add_affected_connectors(struct drm_atomic_state *state,
>>                                  struct drm_crtc *crtc);
>> diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
>> index 758a176e7b57..8701ebcc68b3 100644
>> --- a/include/drm/drm_connector.h
>> +++ b/include/drm/drm_connector.h
>> @@ -425,6 +425,19 @@ struct drm_connector_state {
>>        * protection. This is most commonly used for HDCP.
>>        */
>>       unsigned int content_protection;
>> +
>> +     /**
>> +      * @writeback_job: Writeback job for writeback connectors
>> +      *
>> +      * Holds the framebuffer for a writeback connector. As the writeback
>> +      * completion may be asynchronous to the normal commit cycle, the
>> +      * writeback job lifetime is managed separately from the normal atomic
>> +      * state by this object.
>> +      *
>> +      * See also: drm_writeback_queue_job() and
>> +      * drm_writeback_signal_completion()
>> +      */
>> +     struct drm_writeback_job *writeback_job;
>>  };
>>
>>  /**
>> diff --git a/include/drm/drm_mode_config.h b/include/drm/drm_mode_config.h
>> index 7569f22ffef6..c012e1148ec0 100644
>> --- a/include/drm/drm_mode_config.h
>> +++ b/include/drm/drm_mode_config.h
>> @@ -779,6 +779,20 @@ struct drm_mode_config {
>>        */
>>       struct drm_property *panel_orientation_property;
>>
>> +     /**
>> +      * @writeback_fb_id_property: Property for writeback connectors, storing
>> +      * the ID of the output framebuffer.
>> +      * See also: drm_writeback_connector_init()
>> +      */
>> +     struct drm_property *writeback_fb_id_property;
>> +     /**
>> +      * @writeback_pixel_formats_property: Property for writeback connectors,
>> +      * storing an array of the supported pixel formats for the writeback
>> +      * engine (read-only).
>> +      * See also: drm_writeback_connector_init()
>> +      */
>> +     struct drm_property *writeback_pixel_formats_property;
>> +
>>       /* dumb ioctl parameters */
>>       uint32_t preferred_depth, prefer_shadow;
>>
>> diff --git a/include/drm/drm_modeset_helper_vtables.h b/include/drm/drm_modeset_helper_vtables.h
>> index 3e76ca805b0f..97d3a810bc85 100644
>> --- a/include/drm/drm_modeset_helper_vtables.h
>> +++ b/include/drm/drm_modeset_helper_vtables.h
>> @@ -974,6 +974,17 @@ struct drm_connector_helper_funcs {
>>        */
>>       int (*atomic_check)(struct drm_connector *connector,
>>                           struct drm_connector_state *state);
>> +
>> +     /**
>> +      * @atomic_commit:
>> +      *
>> +      * For write-back connectors, this is the point to commit the write-
>> +      * back job to hw.
>> +      *
>> +      * This callback is used by the atomic modeset helpers.
>> +      */
>> +     void (*atomic_commit)(struct drm_connector *connector,
>> +                           struct drm_writeback_job *writeback_job);
>>  };
>>
>>  /**
>> diff --git a/include/drm/drm_writeback.h b/include/drm/drm_writeback.h
>> new file mode 100644
>> index 000000000000..0bb95fd4907d
>> --- /dev/null
>> +++ b/include/drm/drm_writeback.h
>> @@ -0,0 +1,89 @@
>> +/*
>> + * (C) COPYRIGHT 2016 ARM Limited. All rights reserved.
>> + * Author: Brian Starkey <brian.starkey@arm.com>
>> + *
>> + * This program is free software and is provided to you under the terms of the
>> + * GNU General Public License version 2 as published by the Free Software
>> + * Foundation, and any use by you of this program is subject to the terms
>> + * of such GNU licence.
>> + */
>> +
>> +#ifndef __DRM_WRITEBACK_H__
>> +#define __DRM_WRITEBACK_H__
>> +#include <drm/drm_connector.h>
>> +#include <drm/drm_encoder.h>
>> +#include <linux/workqueue.h>
>> +
>> +struct drm_writeback_connector {
>> +     struct drm_connector base;
>> +
>> +     /**
>> +      * @encoder: Internal encoder used by the connector to fulfill
>> +      * the DRM framework requirements. The users of the
>> +      * @drm_writeback_connector control the behaviour of the @encoder
>> +      * by passing the @enc_funcs parameter to drm_writeback_connector_init()
>> +      * function.
>> +      */
>> +     struct drm_encoder encoder;
>> +
>> +     /**
>> +      * @pixel_formats_blob_ptr:
>> +      *
>> +      * DRM blob property data for the pixel formats list on writeback
>> +      * connectors
>> +      * See also drm_writeback_connector_init()
>> +      */
>> +     struct drm_property_blob *pixel_formats_blob_ptr;
>> +
>> +     /** @job_lock: Protects job_queue */
>> +     spinlock_t job_lock;
>> +
>> +     /**
>> +      * @job_queue:
>> +      *
>> +      * Holds a list of a connector's writeback jobs; the last item is the
>> +      * most recent. The first item may be either waiting for the hardware
>> +      * to begin writing, or currently being written.
>> +      *
>> +      * See also: drm_writeback_queue_job() and
>> +      * drm_writeback_signal_completion()
>> +      */
>> +     struct list_head job_queue;
>> +};
>> +#define to_wb_connector(x) container_of(x, struct drm_writeback_connector, base)
>> +
>> +struct drm_writeback_job {
>> +     /**
>> +      * @cleanup_work:
>> +      *
>> +      * Used to allow drm_writeback_signal_completion to defer dropping the
>> +      * framebuffer reference to a workqueue.
>> +      */
>> +     struct work_struct cleanup_work;
>> +     /**
>> +      * @list_entry:
>> +      *
>> +      * List item for the connector's @job_queue
>> +      */
>> +     struct list_head list_entry;
>> +     /**
>> +      * @fb:
>> +      *
>> +      * Framebuffer to be written to by the writeback connector. Do not set
>> +      * directly, use drm_atomic_set_writeback_fb_for_connector()
>> +      */
>> +     struct drm_framebuffer *fb;
>> +};
>> +
>> +int drm_writeback_connector_init(struct drm_device *dev,
>> +                              struct drm_writeback_connector *wb_connector,
>> +                              const struct drm_connector_funcs *con_funcs,
>> +                              const struct drm_encoder_helper_funcs *enc_helper_funcs,
>> +                              const u32 *formats, int n_formats);
>> +
>> +void drm_writeback_queue_job(struct drm_writeback_connector *wb_connector,
>> +                          struct drm_writeback_job *job);
>> +
>> +void drm_writeback_cleanup_job(struct drm_writeback_job *job);
>> +void drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector);
>> +#endif
>> diff --git a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h
>> index 2c575794fb52..7b47e184e95e 100644
>> --- a/include/uapi/drm/drm_mode.h
>> +++ b/include/uapi/drm/drm_mode.h
>> @@ -338,6 +338,7 @@ enum drm_mode_subconnector {
>>  #define DRM_MODE_CONNECTOR_VIRTUAL      15
>>  #define DRM_MODE_CONNECTOR_DSI               16
>>  #define DRM_MODE_CONNECTOR_DPI               17
>> +#define DRM_MODE_CONNECTOR_WRITEBACK 18
>>
>>  struct drm_mode_get_connector {
>>
>> --
>> 2.14.3
>>
>
> --
> ====================
> | I would like to |
> | fix the world,  |
> | but they're not |
> | giving me the   |
>  \ source code!  /
>   ---------------
>     ¯\_(ツ)_/¯

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [RFC 1/4] drm: Add writeback connector type
  2018-02-23 14:24             ` Rob Clark
@ 2018-02-23 14:27                 ` Liviu Dudau
  -1 siblings, 0 replies; 41+ messages in thread
From: Liviu Dudau @ 2018-02-23 14:27 UTC (permalink / raw)
  To: Rob Clark
  Cc: Jonathan Corbet, David Airlie, linux-arm-msm,
	linux-doc-u79uwXL29TY76Z2rM5mHXA, Maarten Lankhorst,
	Linux Kernel Mailing List, dri-devel, Sean Paul, Gustavo Padovan,
	Mihail Atanassov, freedreno, Brian Starkey

On Fri, Feb 23, 2018 at 09:24:10AM -0500, Rob Clark wrote:
> On Fri, Feb 23, 2018 at 9:00 AM, Liviu Dudau <liviu.dudau@arm.com> wrote:
> > Hi Rob,
> >
> > On Fri, Feb 23, 2018 at 08:17:51AM -0500, Rob Clark wrote:
> >> From: Brian Starkey <brian.starkey@arm.com>
> >>
> >> Writeback connectors represent writeback engines which can write the
> >> CRTC output to a memory framebuffer. Add a writeback connector type and
> >> related support functions.
> >>
> >> Drivers should initialize a writeback connector with
> >> drm_writeback_connector_init() which takes care of setting up all the
> >> writeback-specific details on top of the normal functionality of
> >> drm_connector_init().
> >>
> >> Writeback connectors have a WRITEBACK_FB_ID property, used to set the
> >> output framebuffer, and a WRITEBACK_PIXEL_FORMATS blob used to expose the
> >> supported writeback formats to userspace.
> >>
> >> When a framebuffer is attached to a writeback connector with the
> >> WRITEBACK_FB_ID property, it is used only once (for the commit in which
> >> it was included), and userspace can never read back the value of
> >> WRITEBACK_FB_ID. WRITEBACK_FB_ID can only be set if the connector is
> >> attached to a CRTC.
> >
> > [snip]
> >
> >> +static bool create_writeback_properties(struct drm_device *dev)
> >> +{
> >> +     struct drm_property *prop;
> >> +
> >> +     if (!dev->mode_config.writeback_fb_id_property) {
> >> +             prop = drm_property_create_object(dev, DRM_MODE_PROP_ATOMIC,
> >> +                                               "WRITEBACK_FB_ID",
> >> +                                               DRM_MODE_OBJECT_FB);
> >> +             if (!prop)
> >> +                     return false;
> >> +             dev->mode_config.writeback_fb_id_property = prop;
> >> +     }
> >> +
> >> +     if (!dev->mode_config.writeback_pixel_formats_property) {
> >> +             prop = drm_property_create(dev, DRM_MODE_PROP_BLOB | DRM_MODE_PROP_IMMUTABLE,
> >> +                                        "WRITEBACK_PIXEL_FORMATS", 0);
> >> +             if (!prop)
> >> +                     return false;
> >> +             dev->mode_config.writeback_pixel_formats_property = prop;
> >> +     }
> >> +
> >> +     return true;
> >> +}
> >
> > based on a buildbot warning and the fact that the next patch starts
> > returning -ENOMEM inside the above function, I have this variant in my
> > internal tree that I was preparing to send out once I've got my i-g-t
> > tests in better shape:
> >
> > +static int create_writeback_properties(struct drm_device *dev)
> > +{
> > +       struct drm_property *prop;
> > +
> > +       if (!dev->mode_config.writeback_fb_id_property) {
> > +               prop = drm_property_create_object(dev, DRM_MODE_PROP_ATOMIC,
> > +                                                 "WRITEBACK_FB_ID",
> > +                                                 DRM_MODE_OBJECT_FB);
> > +               if (!prop)
> > +                       return -ENOMEM;
> > +               dev->mode_config.writeback_fb_id_property = prop;
> > +       }
> > +
> > +       if (!dev->mode_config.writeback_pixel_formats_property) {
> > +               prop = drm_property_create(dev, DRM_MODE_PROP_BLOB | DRM_MODE_PROP_IMMUTABLE,
> > +                                          "WRITEBACK_PIXEL_FORMATS", 0);
> > +               if (!prop)
> > +                       return -ENOMEM;
> > +               dev->mode_config.writeback_pixel_formats_property = prop;
> > +       }
> > +
> > +       return 0;
> > +}
> >
> >
> > Feel free to use this version in the next update if you're going to send
> > one, otherwise I guess we could be patching it later.
> >
> 
> hmm, I meant to keep my addition of funcs->atomic_commit() as a
> separate fixup patch so it would be easier for you to fold back into
> your patchset, but accidentally squashed it at some point and was too
> lazy to split it out again.  Sorry about that.

I'm not too fussed about who pushes Brian's framework patches into
drm-next so I don't mind at all you merging your addition. Just wanted
to make sure we're on the same page (didn't know you're going to send
your series this week).

I also feel I need to appologise for my delay in getting the i-g-t
patches into shape, I've been splitting myself between various other
tasks, not all kernel related.

Best regards,
Liviu

> 
> BR,
> -R
> 
> > Best regards,
> > Liviu
> >
> >
> >> +
> >> +static const struct drm_encoder_funcs drm_writeback_encoder_funcs = {
> >> +     .destroy = drm_encoder_cleanup,
> >> +};
> >> +
> >> +/**
> >> + * drm_writeback_connector_init - Initialize a writeback connector and its properties
> >> + * @dev: DRM device
> >> + * @wb_connector: Writeback connector to initialize
> >> + * @con_funcs: Connector funcs vtable
> >> + * @enc_helper_funcs: Encoder helper funcs vtable to be used by the internal encoder
> >> + * @formats: Array of supported pixel formats for the writeback engine
> >> + * @n_formats: Length of the formats array
> >> + *
> >> + * This function creates the writeback-connector-specific properties if they
> >> + * have not been already created, initializes the connector as
> >> + * type DRM_MODE_CONNECTOR_WRITEBACK, and correctly initializes the property
> >> + * values. It will also create an internal encoder associated with the
> >> + * drm_writeback_connector and set it to use the @enc_helper_funcs vtable for
> >> + * the encoder helper.
> >> + *
> >> + * Drivers should always use this function instead of drm_connector_init() to
> >> + * set up writeback connectors.
> >> + *
> >> + * Returns: 0 on success, or a negative error code
> >> + */
> >> +int drm_writeback_connector_init(struct drm_device *dev,
> >> +                              struct drm_writeback_connector *wb_connector,
> >> +                              const struct drm_connector_funcs *con_funcs,
> >> +                              const struct drm_encoder_helper_funcs *enc_helper_funcs,
> >> +                              const u32 *formats, int n_formats)
> >> +{
> >> +     int ret;
> >> +     struct drm_property_blob *blob;
> >> +     struct drm_connector *connector = &wb_connector->base;
> >> +     struct drm_mode_config *config = &dev->mode_config;
> >> +
> >> +     if (!create_writeback_properties(dev))
> >> +             return -EINVAL;
> >> +
> >> +     blob = drm_property_create_blob(dev, n_formats * sizeof(*formats),
> >> +                                     formats);
> >> +     if (IS_ERR(blob))
> >> +             return PTR_ERR(blob);
> >> +
> >> +     drm_encoder_helper_add(&wb_connector->encoder, enc_helper_funcs);
> >> +     ret = drm_encoder_init(dev, &wb_connector->encoder,
> >> +                            &drm_writeback_encoder_funcs,
> >> +                            DRM_MODE_ENCODER_VIRTUAL, NULL);
> >> +     if (ret)
> >> +             goto fail;
> >> +
> >> +     connector->interlace_allowed = 0;
> >> +
> >> +     ret = drm_connector_init(dev, connector, con_funcs,
> >> +                              DRM_MODE_CONNECTOR_WRITEBACK);
> >> +     if (ret)
> >> +             goto connector_fail;
> >> +
> >> +     ret = drm_mode_connector_attach_encoder(connector,
> >> +                                             &wb_connector->encoder);
> >> +     if (ret)
> >> +             goto attach_fail;
> >> +
> >> +     INIT_LIST_HEAD(&wb_connector->job_queue);
> >> +     spin_lock_init(&wb_connector->job_lock);
> >> +
> >> +     drm_object_attach_property(&connector->base,
> >> +                                config->writeback_fb_id_property, 0);
> >> +
> >> +     drm_object_attach_property(&connector->base,
> >> +                                config->writeback_pixel_formats_property,
> >> +                                blob->base.id);
> >> +     wb_connector->pixel_formats_blob_ptr = blob;
> >> +
> >> +     return 0;
> >> +
> >> +attach_fail:
> >> +     drm_connector_cleanup(connector);
> >> +connector_fail:
> >> +     drm_encoder_cleanup(&wb_connector->encoder);
> >> +fail:
> >> +     drm_property_unreference_blob(blob);
> >> +     return ret;
> >> +}
> >> +EXPORT_SYMBOL(drm_writeback_connector_init);
> >> +
> >> +/**
> >> + * drm_writeback_queue_job - Queue a writeback job for later signalling
> >> + * @wb_connector: The writeback connector to queue a job on
> >> + * @job: The job to queue
> >> + *
> >> + * This function adds a job to the job_queue for a writeback connector. It
> >> + * should be considered to take ownership of the writeback job, and so any other
> >> + * references to the job must be cleared after calling this function.
> >> + *
> >> + * Drivers must ensure that for a given writeback connector, jobs are queued in
> >> + * exactly the same order as they will be completed by the hardware (and
> >> + * signaled via drm_writeback_signal_completion).
> >> + *
> >> + * For every call to drm_writeback_queue_job() there must be exactly one call to
> >> + * drm_writeback_signal_completion()
> >> + *
> >> + * See also: drm_writeback_signal_completion()
> >> + */
> >> +void drm_writeback_queue_job(struct drm_writeback_connector *wb_connector,
> >> +                          struct drm_writeback_job *job)
> >> +{
> >> +     unsigned long flags;
> >> +
> >> +     spin_lock_irqsave(&wb_connector->job_lock, flags);
> >> +     list_add_tail(&job->list_entry, &wb_connector->job_queue);
> >> +     spin_unlock_irqrestore(&wb_connector->job_lock, flags);
> >> +}
> >> +EXPORT_SYMBOL(drm_writeback_queue_job);
> >> +
> >> +/**
> >> + * drm_writeback_cleanup_job - Cleanup and free a writeback job
> >> + * @job: The writeback job to free
> >> + *
> >> + * Drops any references held by the writeback job, and frees the structure.
> >> + */
> >> +void drm_writeback_cleanup_job(struct drm_writeback_job *job)
> >> +{
> >> +     if (!job)
> >> +             return;
> >> +
> >> +     if (job->fb)
> >> +             drm_framebuffer_unreference(job->fb);
> >> +     kfree(job);
> >> +}
> >> +EXPORT_SYMBOL(drm_writeback_cleanup_job);
> >> +
> >> +/*
> >> + * @cleanup_work: deferred cleanup of a writeback job
> >> + *
> >> + * The job cannot be cleaned up directly in drm_writeback_signal_completion,
> >> + * because it may be called in interrupt context. Dropping the framebuffer
> >> + * reference can sleep, and so the cleanup is deferred to a workqueue.
> >> + */
> >> +static void cleanup_work(struct work_struct *work)
> >> +{
> >> +     struct drm_writeback_job *job = container_of(work,
> >> +                                                  struct drm_writeback_job,
> >> +                                                  cleanup_work);
> >> +     drm_writeback_cleanup_job(job);
> >> +}
> >> +
> >> +/**
> >> + * drm_writeback_signal_completion - Signal the completion of a writeback job
> >> + * @wb_connector: The writeback connector whose job is complete
> >> + *
> >> + * Drivers should call this to signal the completion of a previously queued
> >> + * writeback job. It should be called as soon as possible after the hardware
> >> + * has finished writing, and may be called from interrupt context.
> >> + * It is the driver's responsibility to ensure that for a given connector, the
> >> + * hardware completes writeback jobs in the same order as they are queued.
> >> + *
> >> + * Unless the driver is holding its own reference to the framebuffer, it must
> >> + * not be accessed after calling this function.
> >> + *
> >> + * See also: drm_writeback_queue_job()
> >> + */
> >> +void
> >> +drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector)
> >> +{
> >> +     unsigned long flags;
> >> +     struct drm_writeback_job *job;
> >> +
> >> +     spin_lock_irqsave(&wb_connector->job_lock, flags);
> >> +     job = list_first_entry_or_null(&wb_connector->job_queue,
> >> +                                    struct drm_writeback_job,
> >> +                                    list_entry);
> >> +     if (job)
> >> +             list_del(&job->list_entry);
> >> +     spin_unlock_irqrestore(&wb_connector->job_lock, flags);
> >> +
> >> +     if (WARN_ON(!job))
> >> +             return;
> >> +
> >> +     INIT_WORK(&job->cleanup_work, cleanup_work);
> >> +     queue_work(system_long_wq, &job->cleanup_work);
> >> +}
> >> +EXPORT_SYMBOL(drm_writeback_signal_completion);
> >> diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
> >> index cf13842a6dbd..d7b0263cc5cf 100644
> >> --- a/include/drm/drm_atomic.h
> >> +++ b/include/drm/drm_atomic.h
> >> @@ -594,6 +594,9 @@ void drm_atomic_set_fence_for_plane(struct drm_plane_state *plane_state,
> >>  int __must_check
> >>  drm_atomic_set_crtc_for_connector(struct drm_connector_state *conn_state,
> >>                                 struct drm_crtc *crtc);
> >> +int drm_atomic_set_writeback_fb_for_connector(
> >> +             struct drm_connector_state *conn_state,
> >> +             struct drm_framebuffer *fb);
> >>  int __must_check
> >>  drm_atomic_add_affected_connectors(struct drm_atomic_state *state,
> >>                                  struct drm_crtc *crtc);
> >> diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
> >> index 758a176e7b57..8701ebcc68b3 100644
> >> --- a/include/drm/drm_connector.h
> >> +++ b/include/drm/drm_connector.h
> >> @@ -425,6 +425,19 @@ struct drm_connector_state {
> >>        * protection. This is most commonly used for HDCP.
> >>        */
> >>       unsigned int content_protection;
> >> +
> >> +     /**
> >> +      * @writeback_job: Writeback job for writeback connectors
> >> +      *
> >> +      * Holds the framebuffer for a writeback connector. As the writeback
> >> +      * completion may be asynchronous to the normal commit cycle, the
> >> +      * writeback job lifetime is managed separately from the normal atomic
> >> +      * state by this object.
> >> +      *
> >> +      * See also: drm_writeback_queue_job() and
> >> +      * drm_writeback_signal_completion()
> >> +      */
> >> +     struct drm_writeback_job *writeback_job;
> >>  };
> >>
> >>  /**
> >> diff --git a/include/drm/drm_mode_config.h b/include/drm/drm_mode_config.h
> >> index 7569f22ffef6..c012e1148ec0 100644
> >> --- a/include/drm/drm_mode_config.h
> >> +++ b/include/drm/drm_mode_config.h
> >> @@ -779,6 +779,20 @@ struct drm_mode_config {
> >>        */
> >>       struct drm_property *panel_orientation_property;
> >>
> >> +     /**
> >> +      * @writeback_fb_id_property: Property for writeback connectors, storing
> >> +      * the ID of the output framebuffer.
> >> +      * See also: drm_writeback_connector_init()
> >> +      */
> >> +     struct drm_property *writeback_fb_id_property;
> >> +     /**
> >> +      * @writeback_pixel_formats_property: Property for writeback connectors,
> >> +      * storing an array of the supported pixel formats for the writeback
> >> +      * engine (read-only).
> >> +      * See also: drm_writeback_connector_init()
> >> +      */
> >> +     struct drm_property *writeback_pixel_formats_property;
> >> +
> >>       /* dumb ioctl parameters */
> >>       uint32_t preferred_depth, prefer_shadow;
> >>
> >> diff --git a/include/drm/drm_modeset_helper_vtables.h b/include/drm/drm_modeset_helper_vtables.h
> >> index 3e76ca805b0f..97d3a810bc85 100644
> >> --- a/include/drm/drm_modeset_helper_vtables.h
> >> +++ b/include/drm/drm_modeset_helper_vtables.h
> >> @@ -974,6 +974,17 @@ struct drm_connector_helper_funcs {
> >>        */
> >>       int (*atomic_check)(struct drm_connector *connector,
> >>                           struct drm_connector_state *state);
> >> +
> >> +     /**
> >> +      * @atomic_commit:
> >> +      *
> >> +      * For write-back connectors, this is the point to commit the write-
> >> +      * back job to hw.
> >> +      *
> >> +      * This callback is used by the atomic modeset helpers.
> >> +      */
> >> +     void (*atomic_commit)(struct drm_connector *connector,
> >> +                           struct drm_writeback_job *writeback_job);
> >>  };
> >>
> >>  /**
> >> diff --git a/include/drm/drm_writeback.h b/include/drm/drm_writeback.h
> >> new file mode 100644
> >> index 000000000000..0bb95fd4907d
> >> --- /dev/null
> >> +++ b/include/drm/drm_writeback.h
> >> @@ -0,0 +1,89 @@
> >> +/*
> >> + * (C) COPYRIGHT 2016 ARM Limited. All rights reserved.
> >> + * Author: Brian Starkey <brian.starkey@arm.com>
> >> + *
> >> + * This program is free software and is provided to you under the terms of the
> >> + * GNU General Public License version 2 as published by the Free Software
> >> + * Foundation, and any use by you of this program is subject to the terms
> >> + * of such GNU licence.
> >> + */
> >> +
> >> +#ifndef __DRM_WRITEBACK_H__
> >> +#define __DRM_WRITEBACK_H__
> >> +#include <drm/drm_connector.h>
> >> +#include <drm/drm_encoder.h>
> >> +#include <linux/workqueue.h>
> >> +
> >> +struct drm_writeback_connector {
> >> +     struct drm_connector base;
> >> +
> >> +     /**
> >> +      * @encoder: Internal encoder used by the connector to fulfill
> >> +      * the DRM framework requirements. The users of the
> >> +      * @drm_writeback_connector control the behaviour of the @encoder
> >> +      * by passing the @enc_funcs parameter to drm_writeback_connector_init()
> >> +      * function.
> >> +      */
> >> +     struct drm_encoder encoder;
> >> +
> >> +     /**
> >> +      * @pixel_formats_blob_ptr:
> >> +      *
> >> +      * DRM blob property data for the pixel formats list on writeback
> >> +      * connectors
> >> +      * See also drm_writeback_connector_init()
> >> +      */
> >> +     struct drm_property_blob *pixel_formats_blob_ptr;
> >> +
> >> +     /** @job_lock: Protects job_queue */
> >> +     spinlock_t job_lock;
> >> +
> >> +     /**
> >> +      * @job_queue:
> >> +      *
> >> +      * Holds a list of a connector's writeback jobs; the last item is the
> >> +      * most recent. The first item may be either waiting for the hardware
> >> +      * to begin writing, or currently being written.
> >> +      *
> >> +      * See also: drm_writeback_queue_job() and
> >> +      * drm_writeback_signal_completion()
> >> +      */
> >> +     struct list_head job_queue;
> >> +};
> >> +#define to_wb_connector(x) container_of(x, struct drm_writeback_connector, base)
> >> +
> >> +struct drm_writeback_job {
> >> +     /**
> >> +      * @cleanup_work:
> >> +      *
> >> +      * Used to allow drm_writeback_signal_completion to defer dropping the
> >> +      * framebuffer reference to a workqueue.
> >> +      */
> >> +     struct work_struct cleanup_work;
> >> +     /**
> >> +      * @list_entry:
> >> +      *
> >> +      * List item for the connector's @job_queue
> >> +      */
> >> +     struct list_head list_entry;
> >> +     /**
> >> +      * @fb:
> >> +      *
> >> +      * Framebuffer to be written to by the writeback connector. Do not set
> >> +      * directly, use drm_atomic_set_writeback_fb_for_connector()
> >> +      */
> >> +     struct drm_framebuffer *fb;
> >> +};
> >> +
> >> +int drm_writeback_connector_init(struct drm_device *dev,
> >> +                              struct drm_writeback_connector *wb_connector,
> >> +                              const struct drm_connector_funcs *con_funcs,
> >> +                              const struct drm_encoder_helper_funcs *enc_helper_funcs,
> >> +                              const u32 *formats, int n_formats);
> >> +
> >> +void drm_writeback_queue_job(struct drm_writeback_connector *wb_connector,
> >> +                          struct drm_writeback_job *job);
> >> +
> >> +void drm_writeback_cleanup_job(struct drm_writeback_job *job);
> >> +void drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector);
> >> +#endif
> >> diff --git a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h
> >> index 2c575794fb52..7b47e184e95e 100644
> >> --- a/include/uapi/drm/drm_mode.h
> >> +++ b/include/uapi/drm/drm_mode.h
> >> @@ -338,6 +338,7 @@ enum drm_mode_subconnector {
> >>  #define DRM_MODE_CONNECTOR_VIRTUAL      15
> >>  #define DRM_MODE_CONNECTOR_DSI               16
> >>  #define DRM_MODE_CONNECTOR_DPI               17
> >> +#define DRM_MODE_CONNECTOR_WRITEBACK 18
> >>
> >>  struct drm_mode_get_connector {
> >>
> >> --
> >> 2.14.3
> >>
> >
> > --
> > ====================
> > | I would like to |
> > | fix the world,  |
> > | but they're not |
> > | giving me the   |
> >  \ source code!  /
> >   ---------------
> >     ¯\_(ツ)_/¯

-- 
====================
| I would like to |
| fix the world,  |
| but they're not |
| giving me the   |
 \ source code!  /
  ---------------
    ¯\_(ツ)_/¯
_______________________________________________
Freedreno mailing list
Freedreno@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/freedreno

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [RFC 1/4] drm: Add writeback connector type
@ 2018-02-23 14:27                 ` Liviu Dudau
  0 siblings, 0 replies; 41+ messages in thread
From: Liviu Dudau @ 2018-02-23 14:27 UTC (permalink / raw)
  To: Rob Clark
  Cc: dri-devel, freedreno, linux-arm-msm, Brian Starkey,
	Mihail Atanassov, Gustavo Padovan, Maarten Lankhorst, Sean Paul,
	David Airlie, Jonathan Corbet, linux-doc,
	Linux Kernel Mailing List

On Fri, Feb 23, 2018 at 09:24:10AM -0500, Rob Clark wrote:
> On Fri, Feb 23, 2018 at 9:00 AM, Liviu Dudau <liviu.dudau@arm.com> wrote:
> > Hi Rob,
> >
> > On Fri, Feb 23, 2018 at 08:17:51AM -0500, Rob Clark wrote:
> >> From: Brian Starkey <brian.starkey@arm.com>
> >>
> >> Writeback connectors represent writeback engines which can write the
> >> CRTC output to a memory framebuffer. Add a writeback connector type and
> >> related support functions.
> >>
> >> Drivers should initialize a writeback connector with
> >> drm_writeback_connector_init() which takes care of setting up all the
> >> writeback-specific details on top of the normal functionality of
> >> drm_connector_init().
> >>
> >> Writeback connectors have a WRITEBACK_FB_ID property, used to set the
> >> output framebuffer, and a WRITEBACK_PIXEL_FORMATS blob used to expose the
> >> supported writeback formats to userspace.
> >>
> >> When a framebuffer is attached to a writeback connector with the
> >> WRITEBACK_FB_ID property, it is used only once (for the commit in which
> >> it was included), and userspace can never read back the value of
> >> WRITEBACK_FB_ID. WRITEBACK_FB_ID can only be set if the connector is
> >> attached to a CRTC.
> >
> > [snip]
> >
> >> +static bool create_writeback_properties(struct drm_device *dev)
> >> +{
> >> +     struct drm_property *prop;
> >> +
> >> +     if (!dev->mode_config.writeback_fb_id_property) {
> >> +             prop = drm_property_create_object(dev, DRM_MODE_PROP_ATOMIC,
> >> +                                               "WRITEBACK_FB_ID",
> >> +                                               DRM_MODE_OBJECT_FB);
> >> +             if (!prop)
> >> +                     return false;
> >> +             dev->mode_config.writeback_fb_id_property = prop;
> >> +     }
> >> +
> >> +     if (!dev->mode_config.writeback_pixel_formats_property) {
> >> +             prop = drm_property_create(dev, DRM_MODE_PROP_BLOB | DRM_MODE_PROP_IMMUTABLE,
> >> +                                        "WRITEBACK_PIXEL_FORMATS", 0);
> >> +             if (!prop)
> >> +                     return false;
> >> +             dev->mode_config.writeback_pixel_formats_property = prop;
> >> +     }
> >> +
> >> +     return true;
> >> +}
> >
> > based on a buildbot warning and the fact that the next patch starts
> > returning -ENOMEM inside the above function, I have this variant in my
> > internal tree that I was preparing to send out once I've got my i-g-t
> > tests in better shape:
> >
> > +static int create_writeback_properties(struct drm_device *dev)
> > +{
> > +       struct drm_property *prop;
> > +
> > +       if (!dev->mode_config.writeback_fb_id_property) {
> > +               prop = drm_property_create_object(dev, DRM_MODE_PROP_ATOMIC,
> > +                                                 "WRITEBACK_FB_ID",
> > +                                                 DRM_MODE_OBJECT_FB);
> > +               if (!prop)
> > +                       return -ENOMEM;
> > +               dev->mode_config.writeback_fb_id_property = prop;
> > +       }
> > +
> > +       if (!dev->mode_config.writeback_pixel_formats_property) {
> > +               prop = drm_property_create(dev, DRM_MODE_PROP_BLOB | DRM_MODE_PROP_IMMUTABLE,
> > +                                          "WRITEBACK_PIXEL_FORMATS", 0);
> > +               if (!prop)
> > +                       return -ENOMEM;
> > +               dev->mode_config.writeback_pixel_formats_property = prop;
> > +       }
> > +
> > +       return 0;
> > +}
> >
> >
> > Feel free to use this version in the next update if you're going to send
> > one, otherwise I guess we could be patching it later.
> >
> 
> hmm, I meant to keep my addition of funcs->atomic_commit() as a
> separate fixup patch so it would be easier for you to fold back into
> your patchset, but accidentally squashed it at some point and was too
> lazy to split it out again.  Sorry about that.

I'm not too fussed about who pushes Brian's framework patches into
drm-next so I don't mind at all you merging your addition. Just wanted
to make sure we're on the same page (didn't know you're going to send
your series this week).

I also feel I need to appologise for my delay in getting the i-g-t
patches into shape, I've been splitting myself between various other
tasks, not all kernel related.

Best regards,
Liviu

> 
> BR,
> -R
> 
> > Best regards,
> > Liviu
> >
> >
> >> +
> >> +static const struct drm_encoder_funcs drm_writeback_encoder_funcs = {
> >> +     .destroy = drm_encoder_cleanup,
> >> +};
> >> +
> >> +/**
> >> + * drm_writeback_connector_init - Initialize a writeback connector and its properties
> >> + * @dev: DRM device
> >> + * @wb_connector: Writeback connector to initialize
> >> + * @con_funcs: Connector funcs vtable
> >> + * @enc_helper_funcs: Encoder helper funcs vtable to be used by the internal encoder
> >> + * @formats: Array of supported pixel formats for the writeback engine
> >> + * @n_formats: Length of the formats array
> >> + *
> >> + * This function creates the writeback-connector-specific properties if they
> >> + * have not been already created, initializes the connector as
> >> + * type DRM_MODE_CONNECTOR_WRITEBACK, and correctly initializes the property
> >> + * values. It will also create an internal encoder associated with the
> >> + * drm_writeback_connector and set it to use the @enc_helper_funcs vtable for
> >> + * the encoder helper.
> >> + *
> >> + * Drivers should always use this function instead of drm_connector_init() to
> >> + * set up writeback connectors.
> >> + *
> >> + * Returns: 0 on success, or a negative error code
> >> + */
> >> +int drm_writeback_connector_init(struct drm_device *dev,
> >> +                              struct drm_writeback_connector *wb_connector,
> >> +                              const struct drm_connector_funcs *con_funcs,
> >> +                              const struct drm_encoder_helper_funcs *enc_helper_funcs,
> >> +                              const u32 *formats, int n_formats)
> >> +{
> >> +     int ret;
> >> +     struct drm_property_blob *blob;
> >> +     struct drm_connector *connector = &wb_connector->base;
> >> +     struct drm_mode_config *config = &dev->mode_config;
> >> +
> >> +     if (!create_writeback_properties(dev))
> >> +             return -EINVAL;
> >> +
> >> +     blob = drm_property_create_blob(dev, n_formats * sizeof(*formats),
> >> +                                     formats);
> >> +     if (IS_ERR(blob))
> >> +             return PTR_ERR(blob);
> >> +
> >> +     drm_encoder_helper_add(&wb_connector->encoder, enc_helper_funcs);
> >> +     ret = drm_encoder_init(dev, &wb_connector->encoder,
> >> +                            &drm_writeback_encoder_funcs,
> >> +                            DRM_MODE_ENCODER_VIRTUAL, NULL);
> >> +     if (ret)
> >> +             goto fail;
> >> +
> >> +     connector->interlace_allowed = 0;
> >> +
> >> +     ret = drm_connector_init(dev, connector, con_funcs,
> >> +                              DRM_MODE_CONNECTOR_WRITEBACK);
> >> +     if (ret)
> >> +             goto connector_fail;
> >> +
> >> +     ret = drm_mode_connector_attach_encoder(connector,
> >> +                                             &wb_connector->encoder);
> >> +     if (ret)
> >> +             goto attach_fail;
> >> +
> >> +     INIT_LIST_HEAD(&wb_connector->job_queue);
> >> +     spin_lock_init(&wb_connector->job_lock);
> >> +
> >> +     drm_object_attach_property(&connector->base,
> >> +                                config->writeback_fb_id_property, 0);
> >> +
> >> +     drm_object_attach_property(&connector->base,
> >> +                                config->writeback_pixel_formats_property,
> >> +                                blob->base.id);
> >> +     wb_connector->pixel_formats_blob_ptr = blob;
> >> +
> >> +     return 0;
> >> +
> >> +attach_fail:
> >> +     drm_connector_cleanup(connector);
> >> +connector_fail:
> >> +     drm_encoder_cleanup(&wb_connector->encoder);
> >> +fail:
> >> +     drm_property_unreference_blob(blob);
> >> +     return ret;
> >> +}
> >> +EXPORT_SYMBOL(drm_writeback_connector_init);
> >> +
> >> +/**
> >> + * drm_writeback_queue_job - Queue a writeback job for later signalling
> >> + * @wb_connector: The writeback connector to queue a job on
> >> + * @job: The job to queue
> >> + *
> >> + * This function adds a job to the job_queue for a writeback connector. It
> >> + * should be considered to take ownership of the writeback job, and so any other
> >> + * references to the job must be cleared after calling this function.
> >> + *
> >> + * Drivers must ensure that for a given writeback connector, jobs are queued in
> >> + * exactly the same order as they will be completed by the hardware (and
> >> + * signaled via drm_writeback_signal_completion).
> >> + *
> >> + * For every call to drm_writeback_queue_job() there must be exactly one call to
> >> + * drm_writeback_signal_completion()
> >> + *
> >> + * See also: drm_writeback_signal_completion()
> >> + */
> >> +void drm_writeback_queue_job(struct drm_writeback_connector *wb_connector,
> >> +                          struct drm_writeback_job *job)
> >> +{
> >> +     unsigned long flags;
> >> +
> >> +     spin_lock_irqsave(&wb_connector->job_lock, flags);
> >> +     list_add_tail(&job->list_entry, &wb_connector->job_queue);
> >> +     spin_unlock_irqrestore(&wb_connector->job_lock, flags);
> >> +}
> >> +EXPORT_SYMBOL(drm_writeback_queue_job);
> >> +
> >> +/**
> >> + * drm_writeback_cleanup_job - Cleanup and free a writeback job
> >> + * @job: The writeback job to free
> >> + *
> >> + * Drops any references held by the writeback job, and frees the structure.
> >> + */
> >> +void drm_writeback_cleanup_job(struct drm_writeback_job *job)
> >> +{
> >> +     if (!job)
> >> +             return;
> >> +
> >> +     if (job->fb)
> >> +             drm_framebuffer_unreference(job->fb);
> >> +     kfree(job);
> >> +}
> >> +EXPORT_SYMBOL(drm_writeback_cleanup_job);
> >> +
> >> +/*
> >> + * @cleanup_work: deferred cleanup of a writeback job
> >> + *
> >> + * The job cannot be cleaned up directly in drm_writeback_signal_completion,
> >> + * because it may be called in interrupt context. Dropping the framebuffer
> >> + * reference can sleep, and so the cleanup is deferred to a workqueue.
> >> + */
> >> +static void cleanup_work(struct work_struct *work)
> >> +{
> >> +     struct drm_writeback_job *job = container_of(work,
> >> +                                                  struct drm_writeback_job,
> >> +                                                  cleanup_work);
> >> +     drm_writeback_cleanup_job(job);
> >> +}
> >> +
> >> +/**
> >> + * drm_writeback_signal_completion - Signal the completion of a writeback job
> >> + * @wb_connector: The writeback connector whose job is complete
> >> + *
> >> + * Drivers should call this to signal the completion of a previously queued
> >> + * writeback job. It should be called as soon as possible after the hardware
> >> + * has finished writing, and may be called from interrupt context.
> >> + * It is the driver's responsibility to ensure that for a given connector, the
> >> + * hardware completes writeback jobs in the same order as they are queued.
> >> + *
> >> + * Unless the driver is holding its own reference to the framebuffer, it must
> >> + * not be accessed after calling this function.
> >> + *
> >> + * See also: drm_writeback_queue_job()
> >> + */
> >> +void
> >> +drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector)
> >> +{
> >> +     unsigned long flags;
> >> +     struct drm_writeback_job *job;
> >> +
> >> +     spin_lock_irqsave(&wb_connector->job_lock, flags);
> >> +     job = list_first_entry_or_null(&wb_connector->job_queue,
> >> +                                    struct drm_writeback_job,
> >> +                                    list_entry);
> >> +     if (job)
> >> +             list_del(&job->list_entry);
> >> +     spin_unlock_irqrestore(&wb_connector->job_lock, flags);
> >> +
> >> +     if (WARN_ON(!job))
> >> +             return;
> >> +
> >> +     INIT_WORK(&job->cleanup_work, cleanup_work);
> >> +     queue_work(system_long_wq, &job->cleanup_work);
> >> +}
> >> +EXPORT_SYMBOL(drm_writeback_signal_completion);
> >> diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
> >> index cf13842a6dbd..d7b0263cc5cf 100644
> >> --- a/include/drm/drm_atomic.h
> >> +++ b/include/drm/drm_atomic.h
> >> @@ -594,6 +594,9 @@ void drm_atomic_set_fence_for_plane(struct drm_plane_state *plane_state,
> >>  int __must_check
> >>  drm_atomic_set_crtc_for_connector(struct drm_connector_state *conn_state,
> >>                                 struct drm_crtc *crtc);
> >> +int drm_atomic_set_writeback_fb_for_connector(
> >> +             struct drm_connector_state *conn_state,
> >> +             struct drm_framebuffer *fb);
> >>  int __must_check
> >>  drm_atomic_add_affected_connectors(struct drm_atomic_state *state,
> >>                                  struct drm_crtc *crtc);
> >> diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
> >> index 758a176e7b57..8701ebcc68b3 100644
> >> --- a/include/drm/drm_connector.h
> >> +++ b/include/drm/drm_connector.h
> >> @@ -425,6 +425,19 @@ struct drm_connector_state {
> >>        * protection. This is most commonly used for HDCP.
> >>        */
> >>       unsigned int content_protection;
> >> +
> >> +     /**
> >> +      * @writeback_job: Writeback job for writeback connectors
> >> +      *
> >> +      * Holds the framebuffer for a writeback connector. As the writeback
> >> +      * completion may be asynchronous to the normal commit cycle, the
> >> +      * writeback job lifetime is managed separately from the normal atomic
> >> +      * state by this object.
> >> +      *
> >> +      * See also: drm_writeback_queue_job() and
> >> +      * drm_writeback_signal_completion()
> >> +      */
> >> +     struct drm_writeback_job *writeback_job;
> >>  };
> >>
> >>  /**
> >> diff --git a/include/drm/drm_mode_config.h b/include/drm/drm_mode_config.h
> >> index 7569f22ffef6..c012e1148ec0 100644
> >> --- a/include/drm/drm_mode_config.h
> >> +++ b/include/drm/drm_mode_config.h
> >> @@ -779,6 +779,20 @@ struct drm_mode_config {
> >>        */
> >>       struct drm_property *panel_orientation_property;
> >>
> >> +     /**
> >> +      * @writeback_fb_id_property: Property for writeback connectors, storing
> >> +      * the ID of the output framebuffer.
> >> +      * See also: drm_writeback_connector_init()
> >> +      */
> >> +     struct drm_property *writeback_fb_id_property;
> >> +     /**
> >> +      * @writeback_pixel_formats_property: Property for writeback connectors,
> >> +      * storing an array of the supported pixel formats for the writeback
> >> +      * engine (read-only).
> >> +      * See also: drm_writeback_connector_init()
> >> +      */
> >> +     struct drm_property *writeback_pixel_formats_property;
> >> +
> >>       /* dumb ioctl parameters */
> >>       uint32_t preferred_depth, prefer_shadow;
> >>
> >> diff --git a/include/drm/drm_modeset_helper_vtables.h b/include/drm/drm_modeset_helper_vtables.h
> >> index 3e76ca805b0f..97d3a810bc85 100644
> >> --- a/include/drm/drm_modeset_helper_vtables.h
> >> +++ b/include/drm/drm_modeset_helper_vtables.h
> >> @@ -974,6 +974,17 @@ struct drm_connector_helper_funcs {
> >>        */
> >>       int (*atomic_check)(struct drm_connector *connector,
> >>                           struct drm_connector_state *state);
> >> +
> >> +     /**
> >> +      * @atomic_commit:
> >> +      *
> >> +      * For write-back connectors, this is the point to commit the write-
> >> +      * back job to hw.
> >> +      *
> >> +      * This callback is used by the atomic modeset helpers.
> >> +      */
> >> +     void (*atomic_commit)(struct drm_connector *connector,
> >> +                           struct drm_writeback_job *writeback_job);
> >>  };
> >>
> >>  /**
> >> diff --git a/include/drm/drm_writeback.h b/include/drm/drm_writeback.h
> >> new file mode 100644
> >> index 000000000000..0bb95fd4907d
> >> --- /dev/null
> >> +++ b/include/drm/drm_writeback.h
> >> @@ -0,0 +1,89 @@
> >> +/*
> >> + * (C) COPYRIGHT 2016 ARM Limited. All rights reserved.
> >> + * Author: Brian Starkey <brian.starkey@arm.com>
> >> + *
> >> + * This program is free software and is provided to you under the terms of the
> >> + * GNU General Public License version 2 as published by the Free Software
> >> + * Foundation, and any use by you of this program is subject to the terms
> >> + * of such GNU licence.
> >> + */
> >> +
> >> +#ifndef __DRM_WRITEBACK_H__
> >> +#define __DRM_WRITEBACK_H__
> >> +#include <drm/drm_connector.h>
> >> +#include <drm/drm_encoder.h>
> >> +#include <linux/workqueue.h>
> >> +
> >> +struct drm_writeback_connector {
> >> +     struct drm_connector base;
> >> +
> >> +     /**
> >> +      * @encoder: Internal encoder used by the connector to fulfill
> >> +      * the DRM framework requirements. The users of the
> >> +      * @drm_writeback_connector control the behaviour of the @encoder
> >> +      * by passing the @enc_funcs parameter to drm_writeback_connector_init()
> >> +      * function.
> >> +      */
> >> +     struct drm_encoder encoder;
> >> +
> >> +     /**
> >> +      * @pixel_formats_blob_ptr:
> >> +      *
> >> +      * DRM blob property data for the pixel formats list on writeback
> >> +      * connectors
> >> +      * See also drm_writeback_connector_init()
> >> +      */
> >> +     struct drm_property_blob *pixel_formats_blob_ptr;
> >> +
> >> +     /** @job_lock: Protects job_queue */
> >> +     spinlock_t job_lock;
> >> +
> >> +     /**
> >> +      * @job_queue:
> >> +      *
> >> +      * Holds a list of a connector's writeback jobs; the last item is the
> >> +      * most recent. The first item may be either waiting for the hardware
> >> +      * to begin writing, or currently being written.
> >> +      *
> >> +      * See also: drm_writeback_queue_job() and
> >> +      * drm_writeback_signal_completion()
> >> +      */
> >> +     struct list_head job_queue;
> >> +};
> >> +#define to_wb_connector(x) container_of(x, struct drm_writeback_connector, base)
> >> +
> >> +struct drm_writeback_job {
> >> +     /**
> >> +      * @cleanup_work:
> >> +      *
> >> +      * Used to allow drm_writeback_signal_completion to defer dropping the
> >> +      * framebuffer reference to a workqueue.
> >> +      */
> >> +     struct work_struct cleanup_work;
> >> +     /**
> >> +      * @list_entry:
> >> +      *
> >> +      * List item for the connector's @job_queue
> >> +      */
> >> +     struct list_head list_entry;
> >> +     /**
> >> +      * @fb:
> >> +      *
> >> +      * Framebuffer to be written to by the writeback connector. Do not set
> >> +      * directly, use drm_atomic_set_writeback_fb_for_connector()
> >> +      */
> >> +     struct drm_framebuffer *fb;
> >> +};
> >> +
> >> +int drm_writeback_connector_init(struct drm_device *dev,
> >> +                              struct drm_writeback_connector *wb_connector,
> >> +                              const struct drm_connector_funcs *con_funcs,
> >> +                              const struct drm_encoder_helper_funcs *enc_helper_funcs,
> >> +                              const u32 *formats, int n_formats);
> >> +
> >> +void drm_writeback_queue_job(struct drm_writeback_connector *wb_connector,
> >> +                          struct drm_writeback_job *job);
> >> +
> >> +void drm_writeback_cleanup_job(struct drm_writeback_job *job);
> >> +void drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector);
> >> +#endif
> >> diff --git a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h
> >> index 2c575794fb52..7b47e184e95e 100644
> >> --- a/include/uapi/drm/drm_mode.h
> >> +++ b/include/uapi/drm/drm_mode.h
> >> @@ -338,6 +338,7 @@ enum drm_mode_subconnector {
> >>  #define DRM_MODE_CONNECTOR_VIRTUAL      15
> >>  #define DRM_MODE_CONNECTOR_DSI               16
> >>  #define DRM_MODE_CONNECTOR_DPI               17
> >> +#define DRM_MODE_CONNECTOR_WRITEBACK 18
> >>
> >>  struct drm_mode_get_connector {
> >>
> >> --
> >> 2.14.3
> >>
> >
> > --
> > ====================
> > | I would like to |
> > | fix the world,  |
> > | but they're not |
> > | giving me the   |
> >  \ source code!  /
> >   ---------------
> >     ¯\_(ツ)_/¯

-- 
====================
| I would like to |
| fix the world,  |
| but they're not |
| giving me the   |
 \ source code!  /
  ---------------
    ¯\_(ツ)_/¯

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [RFC 1/4] drm: Add writeback connector type
  2018-02-23 13:17     ` Rob Clark
@ 2018-02-23 15:59         ` Sean Paul
  -1 siblings, 0 replies; 41+ messages in thread
From: Sean Paul @ 2018-02-23 15:59 UTC (permalink / raw)
  To: Rob Clark
  Cc: Jonathan Corbet, Maarten Lankhorst,
	linux-arm-msm-u79uwXL29TY76Z2rM5mHXA,
	linux-doc-u79uwXL29TY76Z2rM5mHXA, Liviu Dudau,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, David Airlie,
	Sean Paul, Gustavo Padovan, Mihail Atanassov,
	freedreno-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, Brian Starkey

On Fri, Feb 23, 2018 at 08:17:51AM -0500, Rob Clark wrote:
> From: Brian Starkey <brian.starkey@arm.com>
> 
> Writeback connectors represent writeback engines which can write the
> CRTC output to a memory framebuffer. Add a writeback connector type and
> related support functions.
> 
> Drivers should initialize a writeback connector with
> drm_writeback_connector_init() which takes care of setting up all the
> writeback-specific details on top of the normal functionality of
> drm_connector_init().
> 
> Writeback connectors have a WRITEBACK_FB_ID property, used to set the
> output framebuffer, and a WRITEBACK_PIXEL_FORMATS blob used to expose the
> supported writeback formats to userspace.
> 
> When a framebuffer is attached to a writeback connector with the
> WRITEBACK_FB_ID property, it is used only once (for the commit in which
> it was included), and userspace can never read back the value of
> WRITEBACK_FB_ID. WRITEBACK_FB_ID can only be set if the connector is
> attached to a CRTC.
> 
> Changes since v1:
>  - Added drm_writeback.c + documentation
>  - Added helper to initialize writeback connector in one go
>  - Added core checks
>  - Squashed into a single commit
>  - Dropped the client cap
>  - Writeback framebuffers are no longer persistent
> 
> Changes since v2:
>  Daniel Vetter:
>  - Subclass drm_connector to drm_writeback_connector
>  - Relax check to allow CRTC to be set without an FB
>  - Add some writeback_ prefixes
>  - Drop PIXEL_FORMATS_SIZE property, as it was unnecessary
>  Gustavo Padovan:
>  - Add drm_writeback_job to handle writeback signalling centrally
> 
> Changes since v3:
>  - Rebased
>  - Rename PIXEL_FORMATS -> WRITEBACK_PIXEL_FORMATS
> 
> Changes since v4:
>  - Added atomic_commit() vfunc to connector helper funcs, so that
>    writeback jobs are committed from atomic helpers
> 
> Signed-off-by: Brian Starkey <brian.starkey@arm.com>
> [rebased and fixed conflicts]
> Signed-off-by: Mihail Atanassov <mihail.atanassov@arm.com>
> Signed-off-by: Liviu Dudau <liviu.dudau@arm.com>
> [rebased and added atomic_commit() vfunc for writeback jobs]
> Signed-off-by: Rob Clark <robdclark@gmail.com>
> ---
>  Documentation/gpu/drm-kms.rst            |   9 ++
>  drivers/gpu/drm/Makefile                 |   2 +-
>  drivers/gpu/drm/drm_atomic.c             | 130 ++++++++++++++++
>  drivers/gpu/drm/drm_atomic_helper.c      |  30 ++++
>  drivers/gpu/drm/drm_connector.c          |   4 +-
>  drivers/gpu/drm/drm_writeback.c          | 257 +++++++++++++++++++++++++++++++
>  include/drm/drm_atomic.h                 |   3 +
>  include/drm/drm_connector.h              |  13 ++
>  include/drm/drm_mode_config.h            |  14 ++
>  include/drm/drm_modeset_helper_vtables.h |  11 ++
>  include/drm/drm_writeback.h              |  89 +++++++++++
>  include/uapi/drm/drm_mode.h              |   1 +
>  12 files changed, 561 insertions(+), 2 deletions(-)
>  create mode 100644 drivers/gpu/drm/drm_writeback.c
>  create mode 100644 include/drm/drm_writeback.h
> 
> diff --git a/Documentation/gpu/drm-kms.rst b/Documentation/gpu/drm-kms.rst
> index 2dcf5b42015d..e7590dd2f71e 100644
> --- a/Documentation/gpu/drm-kms.rst
> +++ b/Documentation/gpu/drm-kms.rst
> @@ -370,6 +370,15 @@ Connector Functions Reference
>  .. kernel-doc:: drivers/gpu/drm/drm_connector.c
>     :export:
>  
> +Writeback Connectors
> +--------------------
> +
> +.. kernel-doc:: drivers/gpu/drm/drm_writeback.c
> +  :doc: overview
> +
> +.. kernel-doc:: drivers/gpu/drm/drm_writeback.c
> +  :export:
> +
>  Encoder Abstraction
>  ===================
>  
> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> index 50093ff4479b..3d708959b224 100644
> --- a/drivers/gpu/drm/Makefile
> +++ b/drivers/gpu/drm/Makefile
> @@ -18,7 +18,7 @@ drm-y       :=	drm_auth.o drm_bufs.o drm_cache.o \
>  		drm_encoder.o drm_mode_object.o drm_property.o \
>  		drm_plane.o drm_color_mgmt.o drm_print.o \
>  		drm_dumb_buffers.o drm_mode_config.o drm_vblank.o \
> -		drm_syncobj.o drm_lease.o
> +		drm_syncobj.o drm_lease.o drm_writeback.o
>  
>  drm-$(CONFIG_DRM_LIB_RANDOM) += lib/drm_random.o
>  drm-$(CONFIG_DRM_VM) += drm_vm.o
> diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
> index 46733d534587..019f131fe8be 100644
> --- a/drivers/gpu/drm/drm_atomic.c
> +++ b/drivers/gpu/drm/drm_atomic.c
> @@ -30,6 +30,7 @@
>  #include <drm/drm_atomic.h>
>  #include <drm/drm_mode.h>
>  #include <drm/drm_print.h>
> +#include <drm/drm_writeback.h>
>  #include <linux/sync_file.h>
>  
>  #include "drm_crtc_internal.h"
> @@ -638,6 +639,46 @@ static void drm_atomic_crtc_print_state(struct drm_printer *p,
>  		crtc->funcs->atomic_print_state(p, state);
>  }
>  
> +/**
> + * drm_atomic_connector_check - check connector state
> + * @connector: connector to check
> + * @state: connector state to check
> + *
> + * Provides core sanity checks for connector state.
> + *
> + * RETURNS:
> + * Zero on success, error code on failure
> + */
> +static int drm_atomic_connector_check(struct drm_connector *connector,
> +		struct drm_connector_state *state)
> +{
> +	struct drm_crtc_state *crtc_state;
> +	struct drm_writeback_job *writeback_job = state->writeback_job;
> +
> +	if ((connector->connector_type != DRM_MODE_CONNECTOR_WRITEBACK) ||
> +	    !writeback_job)
> +		return 0;
> +
> +	if (writeback_job->fb && !state->crtc) {
> +		DRM_DEBUG_ATOMIC("[CONNECTOR:%d:%s] framebuffer without CRTC\n",
> +				 connector->base.id, connector->name);
> +		return -EINVAL;
> +	}
> +
> +	if (state->crtc)
> +		crtc_state = drm_atomic_get_existing_crtc_state(state->state,
> +								state->crtc);
> +
> +	if (writeback_job->fb && !crtc_state->active) {

Checking for writeback_job->fb is redundant here and above since it's checked
first thing as early exit criteria.

> +		DRM_DEBUG_ATOMIC("[CONNECTOR:%d:%s] has framebuffer, but [CRTC:%d] is off\n",
> +				 connector->base.id, connector->name,
> +				 state->crtc->base.id);
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
>  /**
>   * drm_atomic_get_plane_state - get plane state
>   * @state: global atomic state object
> @@ -1230,6 +1271,12 @@ static int drm_atomic_connector_set_property(struct drm_connector *connector,
>  			return -EINVAL;
>  		}
>  		state->content_protection = val;
> +	} else if (property == config->writeback_fb_id_property) {
> +		struct drm_framebuffer *fb = drm_framebuffer_lookup(dev, NULL, val);
> +		int ret = drm_atomic_set_writeback_fb_for_connector(state, fb);
> +		if (fb)
> +			drm_framebuffer_unreference(fb);
> +		return ret;
>  	} else if (connector->funcs->atomic_set_property) {
>  		return connector->funcs->atomic_set_property(connector,
>  				state, property, val);
> @@ -1311,6 +1358,9 @@ drm_atomic_connector_get_property(struct drm_connector *connector,
>  		*val = state->scaling_mode;
>  	} else if (property == connector->content_protection_property) {
>  		*val = state->content_protection;
> +	} else if (property == config->writeback_fb_id_property) {
> +		/* Writeback framebuffer is one-shot, write and forget */
> +		*val = 0;
>  	} else if (connector->funcs->atomic_get_property) {
>  		return connector->funcs->atomic_get_property(connector,
>  				state, property, val);
> @@ -1518,6 +1568,75 @@ drm_atomic_set_crtc_for_connector(struct drm_connector_state *conn_state,
>  }
>  EXPORT_SYMBOL(drm_atomic_set_crtc_for_connector);
>  
> +/*
> + * drm_atomic_get_writeback_job - return or allocate a writeback job
> + * @conn_state: Connector state to get the job for
> + *
> + * Writeback jobs have a different lifetime to the atomic state they are
> + * associated with. This convenience function takes care of allocating a job
> + * if there isn't yet one associated with the connector state, otherwise
> + * it just returns the existing job.
> + *
> + * Returns: The writeback job for the given connector state
> + */
> +static struct drm_writeback_job *
> +drm_atomic_get_writeback_job(struct drm_connector_state *conn_state)
> +{
> +	WARN_ON(conn_state->connector->connector_type !=
> +		DRM_MODE_CONNECTOR_WRITEBACK);
> +
> +	if (!conn_state->writeback_job)
> +		conn_state->writeback_job =
> +			kzalloc(sizeof(*conn_state->writeback_job), GFP_KERNEL);
> +
> +	return conn_state->writeback_job;
> +}
> +
> +/**
> + * drm_atomic_set_writeback_fb_for_connector - set writeback framebuffer
> + * @conn_state: atomic state object for the connector
> + * @fb: fb to use for the connector
> + *
> + * This is used to set the framebuffer for a writeback connector, which outputs
> + * to a buffer instead of an actual physical connector.
> + * Changing the assigned framebuffer requires us to grab a reference to the new
> + * fb and drop the reference to the old fb, if there is one. This function
> + * takes care of all these details besides updating the pointer in the
> + * state object itself.
> + *
> + * Note: The only way conn_state can already have an fb set is if the commit
> + * sets the property more than once.
> + *
> + * See also: drm_writeback_connector_init()
> + *
> + * Returns: 0 on success
> + */
> +int drm_atomic_set_writeback_fb_for_connector(
> +		struct drm_connector_state *conn_state,
> +		struct drm_framebuffer *fb)
> +{
> +	struct drm_writeback_job *job =
> +		drm_atomic_get_writeback_job(conn_state);
> +	if (!job)
> +		return -ENOMEM;
> +
> +	if (job->fb)
> +		drm_framebuffer_unreference(job->fb);
> +	if (fb)
> +		drm_framebuffer_reference(fb);
> +	job->fb = fb;
> +
> +	if (fb)
> +		DRM_DEBUG_ATOMIC("Set [FB:%d] for connector state %p\n",
> +				 fb->base.id, conn_state);
> +	else
> +		DRM_DEBUG_ATOMIC("Set [NOFB] for connector state %p\n",
> +				 conn_state);
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL(drm_atomic_set_writeback_fb_for_connector);
> +
>  /**
>   * drm_atomic_add_affected_connectors - add connectors for crtc
>   * @state: atomic state
> @@ -1636,6 +1755,8 @@ int drm_atomic_check_only(struct drm_atomic_state *state)
>  	struct drm_plane_state *plane_state;
>  	struct drm_crtc *crtc;
>  	struct drm_crtc_state *crtc_state;
> +	struct drm_connector *conn;
> +	struct drm_connector_state *conn_state;
>  	int i, ret = 0;
>  
>  	DRM_DEBUG_ATOMIC("checking %p\n", state);
> @@ -1658,6 +1779,15 @@ int drm_atomic_check_only(struct drm_atomic_state *state)
>  		}
>  	}
>  
> +	for_each_new_connector_in_state(state, conn, conn_state, i) {
> +		ret = drm_atomic_connector_check(conn, conn_state);
> +		if (ret) {
> +			DRM_DEBUG_ATOMIC("[CONNECTOR:%d:%s] atomic core check failed\n",
> +					 conn->base.id, conn->name);
> +			return ret;
> +		}
> +	}
> +
>  	if (config->funcs->atomic_check)
>  		ret = config->funcs->atomic_check(state->dev, state);
>  
> diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c
> index ae3cbfe9e01c..12b910755d84 100644
> --- a/drivers/gpu/drm/drm_atomic_helper.c
> +++ b/drivers/gpu/drm/drm_atomic_helper.c
> @@ -30,6 +30,7 @@
>  #include <drm/drm_plane_helper.h>
>  #include <drm/drm_crtc_helper.h>
>  #include <drm/drm_atomic_helper.h>
> +#include <drm/drm_writeback.h>
>  #include <linux/dma-fence.h>
>  
>  #include "drm_crtc_helper_internal.h"
> @@ -1159,6 +1160,27 @@ void drm_atomic_helper_commit_modeset_disables(struct drm_device *dev,
>  }
>  EXPORT_SYMBOL(drm_atomic_helper_commit_modeset_disables);
>  
> +static void commit_writebacks(struct drm_device *dev, struct drm_atomic_state *old_state)
> +{
> +	struct drm_connector *connector;
> +	struct drm_connector_state *new_conn_state;
> +	int i;
> +
> +	for_each_new_connector_in_state(old_state, connector, new_conn_state, i) {
> +		const struct drm_connector_helper_funcs *funcs;
> +
> +		funcs = connector->helper_private;
> +
> +		if (new_conn_state->writeback_job &&
> +		    new_conn_state->writeback_job->fb) {
> +			WARN_ON(connector->connector_type !=
> +				DRM_MODE_CONNECTOR_WRITEBACK);
> +			funcs->atomic_commit(connector,
> +					     new_conn_state->writeback_job);
> +		}
> +	}
> +}
> +
>  /**
>   * drm_atomic_helper_commit_modeset_enables - modeset commit to enable outputs
>   * @dev: DRM device
> @@ -1238,6 +1260,8 @@ void drm_atomic_helper_commit_modeset_enables(struct drm_device *dev,
>  
>  		drm_bridge_enable(encoder->bridge);
>  	}
> +
> +	commit_writebacks(dev, old_state);
>  }
>  EXPORT_SYMBOL(drm_atomic_helper_commit_modeset_enables);
>  
> @@ -3627,6 +3651,9 @@ __drm_atomic_helper_connector_duplicate_state(struct drm_connector *connector,
>  	if (state->crtc)
>  		drm_connector_get(connector);
>  	state->commit = NULL;
> +
> +	/* Don't copy over a writeback job, they are used only once */
> +	state->writeback_job = NULL;
>  }
>  EXPORT_SYMBOL(__drm_atomic_helper_connector_duplicate_state);
>  
> @@ -3756,6 +3783,9 @@ __drm_atomic_helper_connector_destroy_state(struct drm_connector_state *state)
>  
>  	if (state->commit)
>  		drm_crtc_commit_put(state->commit);
> +
> +	if (state->writeback_job)
> +		drm_writeback_cleanup_job(state->writeback_job);
>  }
>  EXPORT_SYMBOL(__drm_atomic_helper_connector_destroy_state);
>  
> diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c
> index 16b9c3810af2..add47f06ae70 100644
> --- a/drivers/gpu/drm/drm_connector.c
> +++ b/drivers/gpu/drm/drm_connector.c
> @@ -87,6 +87,7 @@ static struct drm_conn_prop_enum_list drm_connector_enum_list[] = {
>  	{ DRM_MODE_CONNECTOR_VIRTUAL, "Virtual" },
>  	{ DRM_MODE_CONNECTOR_DSI, "DSI" },
>  	{ DRM_MODE_CONNECTOR_DPI, "DPI" },
> +	{ DRM_MODE_CONNECTOR_WRITEBACK, "Writeback" },
>  };
>  
>  void drm_connector_ida_init(void)
> @@ -249,7 +250,8 @@ int drm_connector_init(struct drm_device *dev,
>  	config->num_connector++;
>  	spin_unlock_irq(&config->connector_list_lock);
>  
> -	if (connector_type != DRM_MODE_CONNECTOR_VIRTUAL)
> +	if (connector_type != DRM_MODE_CONNECTOR_VIRTUAL &&
> +	    connector_type != DRM_MODE_CONNECTOR_WRITEBACK)
>  		drm_object_attach_property(&connector->base,
>  					      config->edid_property,
>  					      0);
> diff --git a/drivers/gpu/drm/drm_writeback.c b/drivers/gpu/drm/drm_writeback.c
> new file mode 100644
> index 000000000000..da61f929cbc3
> --- /dev/null
> +++ b/drivers/gpu/drm/drm_writeback.c
> @@ -0,0 +1,257 @@
> +/*
> + * (C) COPYRIGHT 2016 ARM Limited. All rights reserved.
> + * Author: Brian Starkey <brian.starkey@arm.com>
> + *
> + * This program is free software and is provided to you under the terms of the
> + * GNU General Public License version 2 as published by the Free Software
> + * Foundation, and any use by you of this program is subject to the terms
> + * of such GNU licence.
> + */
> +
> +#include <drm/drm_crtc.h>
> +#include <drm/drm_modeset_helper_vtables.h>
> +#include <drm/drm_property.h>
> +#include <drm/drm_writeback.h>
> +#include <drm/drmP.h>
> +
> +/**
> + * DOC: overview
> + *
> + * Writeback connectors are used to expose hardware which can write the output
> + * from a CRTC to a memory buffer. They are used and act similarly to other
> + * types of connectors, with some important differences:
> + *  - Writeback connectors don't provide a way to output visually to the user.
> + *  - Writeback connectors should always report as "disconnected" (so that
> + *    clients which don't understand them will ignore them).

Have we considered hiding writeback behind a client cap instead?

> + *  - Writeback connectors don't have EDID.
> + *
> + * A framebuffer may only be attached to a writeback connector when the
> + * connector is attached to a CRTC. The WRITEBACK_FB_ID property which sets the
> + * framebuffer applies only to a single commit (see below). A framebuffer may
> + * not be attached while the CRTC is off.
> + *
> + * Writeback connectors have some additional properties, which userspace
> + * can use to query and control them:
> + *
> + *  "WRITEBACK_FB_ID":
> + *	Write-only object property storing a DRM_MODE_OBJECT_FB: it stores the
> + *	framebuffer to be written by the writeback connector. This property is
> + *	similar to the FB_ID property on planes, but will always read as zero
> + *	and is not preserved across commits.
> + *	Userspace must set this property to an output buffer every time it
> + *	wishes the buffer to get filled.
> + *
> + *  "WRITEBACK_PIXEL_FORMATS":
> + *	Immutable blob property to store the supported pixel formats table. The
> + *	data is an array of u32 DRM_FORMAT_* fourcc values.
> + *	Userspace can use this blob to find out what pixel formats are supported
> + *	by the connector's writeback engine.
> + */
> +
> +static bool create_writeback_properties(struct drm_device *dev)
> +{
> +	struct drm_property *prop;
> +
> +	if (!dev->mode_config.writeback_fb_id_property) {

Somewhat an aside, but we should probably come up with more formal rules on
where to stick properties. We have some in drm_connector, which I interpret as
"connector-only properties", but we also have a bunch in mode_config.

For optional properties which will generally only apply to one connector on a
system, I'd be inclined to put it in connector. 

> +		prop = drm_property_create_object(dev, DRM_MODE_PROP_ATOMIC,
> +						  "WRITEBACK_FB_ID",
> +						  DRM_MODE_OBJECT_FB);
> +		if (!prop)
> +			return false;
> +		dev->mode_config.writeback_fb_id_property = prop;
> +	}
> +
> +	if (!dev->mode_config.writeback_pixel_formats_property) {
> +		prop = drm_property_create(dev, DRM_MODE_PROP_BLOB | DRM_MODE_PROP_IMMUTABLE,
> +					   "WRITEBACK_PIXEL_FORMATS", 0);

Does it make sense to expose the formats if u/s doesn't support atomic and can't
use writeback? Perhaps this should also be DRM_MODE_PROP_ATOMIC.


> +		if (!prop)
> +			return false;
> +		dev->mode_config.writeback_pixel_formats_property = prop;
> +	}
> +
> +	return true;
> +}
> +
> +static const struct drm_encoder_funcs drm_writeback_encoder_funcs = {
> +	.destroy = drm_encoder_cleanup,
> +};
> +
> +/**
> + * drm_writeback_connector_init - Initialize a writeback connector and its properties
> + * @dev: DRM device
> + * @wb_connector: Writeback connector to initialize
> + * @con_funcs: Connector funcs vtable
> + * @enc_helper_funcs: Encoder helper funcs vtable to be used by the internal encoder
> + * @formats: Array of supported pixel formats for the writeback engine
> + * @n_formats: Length of the formats array
> + *
> + * This function creates the writeback-connector-specific properties if they
> + * have not been already created, initializes the connector as
> + * type DRM_MODE_CONNECTOR_WRITEBACK, and correctly initializes the property
> + * values. It will also create an internal encoder associated with the
> + * drm_writeback_connector and set it to use the @enc_helper_funcs vtable for
> + * the encoder helper.
> + *
> + * Drivers should always use this function instead of drm_connector_init() to
> + * set up writeback connectors.
> + *
> + * Returns: 0 on success, or a negative error code
> + */
> +int drm_writeback_connector_init(struct drm_device *dev,
> +				 struct drm_writeback_connector *wb_connector,
> +				 const struct drm_connector_funcs *con_funcs,
> +				 const struct drm_encoder_helper_funcs *enc_helper_funcs,
> +				 const u32 *formats, int n_formats)
> +{
> +	int ret;
> +	struct drm_property_blob *blob;
> +	struct drm_connector *connector = &wb_connector->base;
> +	struct drm_mode_config *config = &dev->mode_config;
> +
> +	if (!create_writeback_properties(dev))
> +		return -EINVAL;
> +
> +	blob = drm_property_create_blob(dev, n_formats * sizeof(*formats),
> +					formats);
> +	if (IS_ERR(blob))
> +		return PTR_ERR(blob);
> +
> +	drm_encoder_helper_add(&wb_connector->encoder, enc_helper_funcs);
> +	ret = drm_encoder_init(dev, &wb_connector->encoder,
> +			       &drm_writeback_encoder_funcs,
> +			       DRM_MODE_ENCODER_VIRTUAL, NULL);
> +	if (ret)
> +		goto fail;
> +
> +	connector->interlace_allowed = 0;
> +
> +	ret = drm_connector_init(dev, connector, con_funcs,
> +				 DRM_MODE_CONNECTOR_WRITEBACK);
> +	if (ret)
> +		goto connector_fail;
> +
> +	ret = drm_mode_connector_attach_encoder(connector,
> +						&wb_connector->encoder);
> +	if (ret)
> +		goto attach_fail;
> +
> +	INIT_LIST_HEAD(&wb_connector->job_queue);
> +	spin_lock_init(&wb_connector->job_lock);
> +
> +	drm_object_attach_property(&connector->base,
> +				   config->writeback_fb_id_property, 0);
> +
> +	drm_object_attach_property(&connector->base,
> +				   config->writeback_pixel_formats_property,
> +				   blob->base.id);
> +	wb_connector->pixel_formats_blob_ptr = blob;
> +
> +	return 0;
> +
> +attach_fail:
> +	drm_connector_cleanup(connector);
> +connector_fail:
> +	drm_encoder_cleanup(&wb_connector->encoder);
> +fail:
> +	drm_property_unreference_blob(blob);
> +	return ret;
> +}
> +EXPORT_SYMBOL(drm_writeback_connector_init);
> +
> +/**
> + * drm_writeback_queue_job - Queue a writeback job for later signalling
> + * @wb_connector: The writeback connector to queue a job on
> + * @job: The job to queue
> + *
> + * This function adds a job to the job_queue for a writeback connector. It
> + * should be considered to take ownership of the writeback job, and so any other
> + * references to the job must be cleared after calling this function.
> + *
> + * Drivers must ensure that for a given writeback connector, jobs are queued in
> + * exactly the same order as they will be completed by the hardware (and
> + * signaled via drm_writeback_signal_completion).
> + *
> + * For every call to drm_writeback_queue_job() there must be exactly one call to
> + * drm_writeback_signal_completion()
> + *
> + * See also: drm_writeback_signal_completion()
> + */
> +void drm_writeback_queue_job(struct drm_writeback_connector *wb_connector,
> +			     struct drm_writeback_job *job)
> +{
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&wb_connector->job_lock, flags);
> +	list_add_tail(&job->list_entry, &wb_connector->job_queue);
> +	spin_unlock_irqrestore(&wb_connector->job_lock, flags);
> +}
> +EXPORT_SYMBOL(drm_writeback_queue_job);
> +
> +/**
> + * drm_writeback_cleanup_job - Cleanup and free a writeback job
> + * @job: The writeback job to free
> + *
> + * Drops any references held by the writeback job, and frees the structure.
> + */
> +void drm_writeback_cleanup_job(struct drm_writeback_job *job)
> +{
> +	if (!job)
> +		return;
> +
> +	if (job->fb)
> +		drm_framebuffer_unreference(job->fb);
> +	kfree(job);
> +}
> +EXPORT_SYMBOL(drm_writeback_cleanup_job);
> +
> +/*
> + * @cleanup_work: deferred cleanup of a writeback job
> + *
> + * The job cannot be cleaned up directly in drm_writeback_signal_completion,
> + * because it may be called in interrupt context. Dropping the framebuffer
> + * reference can sleep, and so the cleanup is deferred to a workqueue.
> + */
> +static void cleanup_work(struct work_struct *work)
> +{
> +	struct drm_writeback_job *job = container_of(work,
> +						     struct drm_writeback_job,
> +						     cleanup_work);
> +	drm_writeback_cleanup_job(job);

TIL that you can free work while using it.

> +}
> +
> +/**
> + * drm_writeback_signal_completion - Signal the completion of a writeback job
> + * @wb_connector: The writeback connector whose job is complete
> + *
> + * Drivers should call this to signal the completion of a previously queued
> + * writeback job. It should be called as soon as possible after the hardware
> + * has finished writing, and may be called from interrupt context.
> + * It is the driver's responsibility to ensure that for a given connector, the
> + * hardware completes writeback jobs in the same order as they are queued.
> + *
> + * Unless the driver is holding its own reference to the framebuffer, it must
> + * not be accessed after calling this function.
> + *
> + * See also: drm_writeback_queue_job()
> + */
> +void
> +drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector)
> +{
> +	unsigned long flags;
> +	struct drm_writeback_job *job;
> +
> +	spin_lock_irqsave(&wb_connector->job_lock, flags);
> +	job = list_first_entry_or_null(&wb_connector->job_queue,
> +				       struct drm_writeback_job,
> +				       list_entry);
> +	if (job)
> +		list_del(&job->list_entry);
> +	spin_unlock_irqrestore(&wb_connector->job_lock, flags);
> +
> +	if (WARN_ON(!job))
> +		return;
> +
> +	INIT_WORK(&job->cleanup_work, cleanup_work);
> +	queue_work(system_long_wq, &job->cleanup_work);
> +}
> +EXPORT_SYMBOL(drm_writeback_signal_completion);
> diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
> index cf13842a6dbd..d7b0263cc5cf 100644
> --- a/include/drm/drm_atomic.h
> +++ b/include/drm/drm_atomic.h
> @@ -594,6 +594,9 @@ void drm_atomic_set_fence_for_plane(struct drm_plane_state *plane_state,
>  int __must_check
>  drm_atomic_set_crtc_for_connector(struct drm_connector_state *conn_state,
>  				  struct drm_crtc *crtc);
> +int drm_atomic_set_writeback_fb_for_connector(
> +		struct drm_connector_state *conn_state,
> +		struct drm_framebuffer *fb);
>  int __must_check
>  drm_atomic_add_affected_connectors(struct drm_atomic_state *state,
>  				   struct drm_crtc *crtc);
> diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
> index 758a176e7b57..8701ebcc68b3 100644
> --- a/include/drm/drm_connector.h
> +++ b/include/drm/drm_connector.h
> @@ -425,6 +425,19 @@ struct drm_connector_state {
>  	 * protection. This is most commonly used for HDCP.
>  	 */
>  	unsigned int content_protection;
> +
> +	/**
> +	 * @writeback_job: Writeback job for writeback connectors
> +	 *
> +	 * Holds the framebuffer for a writeback connector. As the writeback
> +	 * completion may be asynchronous to the normal commit cycle, the
> +	 * writeback job lifetime is managed separately from the normal atomic
> +	 * state by this object.
> +	 *
> +	 * See also: drm_writeback_queue_job() and
> +	 * drm_writeback_signal_completion()
> +	 */
> +	struct drm_writeback_job *writeback_job;
>  };
>  
>  /**
> diff --git a/include/drm/drm_mode_config.h b/include/drm/drm_mode_config.h
> index 7569f22ffef6..c012e1148ec0 100644
> --- a/include/drm/drm_mode_config.h
> +++ b/include/drm/drm_mode_config.h
> @@ -779,6 +779,20 @@ struct drm_mode_config {
>  	 */
>  	struct drm_property *panel_orientation_property;
>  
> +	/**
> +	 * @writeback_fb_id_property: Property for writeback connectors, storing
> +	 * the ID of the output framebuffer.
> +	 * See also: drm_writeback_connector_init()
> +	 */
> +	struct drm_property *writeback_fb_id_property;
> +	/**
> +	 * @writeback_pixel_formats_property: Property for writeback connectors,
> +	 * storing an array of the supported pixel formats for the writeback
> +	 * engine (read-only).
> +	 * See also: drm_writeback_connector_init()
> +	 */
> +	struct drm_property *writeback_pixel_formats_property;
> +
>  	/* dumb ioctl parameters */
>  	uint32_t preferred_depth, prefer_shadow;
>  
> diff --git a/include/drm/drm_modeset_helper_vtables.h b/include/drm/drm_modeset_helper_vtables.h
> index 3e76ca805b0f..97d3a810bc85 100644
> --- a/include/drm/drm_modeset_helper_vtables.h
> +++ b/include/drm/drm_modeset_helper_vtables.h
> @@ -974,6 +974,17 @@ struct drm_connector_helper_funcs {
>  	 */
>  	int (*atomic_check)(struct drm_connector *connector,
>  			    struct drm_connector_state *state);
> +
> +	/**
> +	 * @atomic_commit:
> +	 *
> +	 * For write-back connectors, this is the point to commit the write-
> +	 * back job to hw.
> +	 *
> +	 * This callback is used by the atomic modeset helpers.
> +	 */
> +	void (*atomic_commit)(struct drm_connector *connector,
> +			      struct drm_writeback_job *writeback_job);
>  };
>  
>  /**
> diff --git a/include/drm/drm_writeback.h b/include/drm/drm_writeback.h
> new file mode 100644
> index 000000000000..0bb95fd4907d
> --- /dev/null
> +++ b/include/drm/drm_writeback.h
> @@ -0,0 +1,89 @@
> +/*
> + * (C) COPYRIGHT 2016 ARM Limited. All rights reserved.
> + * Author: Brian Starkey <brian.starkey@arm.com>
> + *
> + * This program is free software and is provided to you under the terms of the
> + * GNU General Public License version 2 as published by the Free Software
> + * Foundation, and any use by you of this program is subject to the terms
> + * of such GNU licence.
> + */
> +
> +#ifndef __DRM_WRITEBACK_H__
> +#define __DRM_WRITEBACK_H__
> +#include <drm/drm_connector.h>
> +#include <drm/drm_encoder.h>
> +#include <linux/workqueue.h>
> +
> +struct drm_writeback_connector {
> +	struct drm_connector base;
> +
> +	/**
> +	 * @encoder: Internal encoder used by the connector to fulfill
> +	 * the DRM framework requirements. The users of the
> +	 * @drm_writeback_connector control the behaviour of the @encoder
> +	 * by passing the @enc_funcs parameter to drm_writeback_connector_init()
> +	 * function.
> +	 */
> +	struct drm_encoder encoder;
> +
> +	/**
> +	 * @pixel_formats_blob_ptr:
> +	 *
> +	 * DRM blob property data for the pixel formats list on writeback
> +	 * connectors
> +	 * See also drm_writeback_connector_init()
> +	 */
> +	struct drm_property_blob *pixel_formats_blob_ptr;
> +
> +	/** @job_lock: Protects job_queue */
> +	spinlock_t job_lock;
> +
> +	/**
> +	 * @job_queue:
> +	 *
> +	 * Holds a list of a connector's writeback jobs; the last item is the
> +	 * most recent. The first item may be either waiting for the hardware
> +	 * to begin writing, or currently being written.
> +	 *
> +	 * See also: drm_writeback_queue_job() and
> +	 * drm_writeback_signal_completion()
> +	 */
> +	struct list_head job_queue;
> +};
> +#define to_wb_connector(x) container_of(x, struct drm_writeback_connector, base)
> +
> +struct drm_writeback_job {
> +	/**
> +	 * @cleanup_work:
> +	 *
> +	 * Used to allow drm_writeback_signal_completion to defer dropping the
> +	 * framebuffer reference to a workqueue.
> +	 */
> +	struct work_struct cleanup_work;
> +	/**
> +	 * @list_entry:
> +	 *
> +	 * List item for the connector's @job_queue
> +	 */
> +	struct list_head list_entry;
> +	/**
> +	 * @fb:
> +	 *
> +	 * Framebuffer to be written to by the writeback connector. Do not set
> +	 * directly, use drm_atomic_set_writeback_fb_for_connector()
> +	 */
> +	struct drm_framebuffer *fb;
> +};
> +
> +int drm_writeback_connector_init(struct drm_device *dev,
> +				 struct drm_writeback_connector *wb_connector,
> +				 const struct drm_connector_funcs *con_funcs,
> +				 const struct drm_encoder_helper_funcs *enc_helper_funcs,
> +				 const u32 *formats, int n_formats);
> +
> +void drm_writeback_queue_job(struct drm_writeback_connector *wb_connector,
> +			     struct drm_writeback_job *job);
> +
> +void drm_writeback_cleanup_job(struct drm_writeback_job *job);
> +void drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector);
> +#endif
> diff --git a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h
> index 2c575794fb52..7b47e184e95e 100644
> --- a/include/uapi/drm/drm_mode.h
> +++ b/include/uapi/drm/drm_mode.h
> @@ -338,6 +338,7 @@ enum drm_mode_subconnector {
>  #define DRM_MODE_CONNECTOR_VIRTUAL      15
>  #define DRM_MODE_CONNECTOR_DSI		16
>  #define DRM_MODE_CONNECTOR_DPI		17
> +#define DRM_MODE_CONNECTOR_WRITEBACK	18
>  
>  struct drm_mode_get_connector {
>  
> -- 
> 2.14.3
> 

-- 
Sean Paul, Software Engineer, Google / Chromium OS
_______________________________________________
Freedreno mailing list
Freedreno@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/freedreno

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [RFC 1/4] drm: Add writeback connector type
@ 2018-02-23 15:59         ` Sean Paul
  0 siblings, 0 replies; 41+ messages in thread
From: Sean Paul @ 2018-02-23 15:59 UTC (permalink / raw)
  To: Rob Clark
  Cc: dri-devel, freedreno, linux-arm-msm, Brian Starkey, Liviu Dudau,
	Mihail Atanassov, Gustavo Padovan, Maarten Lankhorst, Sean Paul,
	David Airlie, Jonathan Corbet, linux-doc, linux-kernel

On Fri, Feb 23, 2018 at 08:17:51AM -0500, Rob Clark wrote:
> From: Brian Starkey <brian.starkey@arm.com>
> 
> Writeback connectors represent writeback engines which can write the
> CRTC output to a memory framebuffer. Add a writeback connector type and
> related support functions.
> 
> Drivers should initialize a writeback connector with
> drm_writeback_connector_init() which takes care of setting up all the
> writeback-specific details on top of the normal functionality of
> drm_connector_init().
> 
> Writeback connectors have a WRITEBACK_FB_ID property, used to set the
> output framebuffer, and a WRITEBACK_PIXEL_FORMATS blob used to expose the
> supported writeback formats to userspace.
> 
> When a framebuffer is attached to a writeback connector with the
> WRITEBACK_FB_ID property, it is used only once (for the commit in which
> it was included), and userspace can never read back the value of
> WRITEBACK_FB_ID. WRITEBACK_FB_ID can only be set if the connector is
> attached to a CRTC.
> 
> Changes since v1:
>  - Added drm_writeback.c + documentation
>  - Added helper to initialize writeback connector in one go
>  - Added core checks
>  - Squashed into a single commit
>  - Dropped the client cap
>  - Writeback framebuffers are no longer persistent
> 
> Changes since v2:
>  Daniel Vetter:
>  - Subclass drm_connector to drm_writeback_connector
>  - Relax check to allow CRTC to be set without an FB
>  - Add some writeback_ prefixes
>  - Drop PIXEL_FORMATS_SIZE property, as it was unnecessary
>  Gustavo Padovan:
>  - Add drm_writeback_job to handle writeback signalling centrally
> 
> Changes since v3:
>  - Rebased
>  - Rename PIXEL_FORMATS -> WRITEBACK_PIXEL_FORMATS
> 
> Changes since v4:
>  - Added atomic_commit() vfunc to connector helper funcs, so that
>    writeback jobs are committed from atomic helpers
> 
> Signed-off-by: Brian Starkey <brian.starkey@arm.com>
> [rebased and fixed conflicts]
> Signed-off-by: Mihail Atanassov <mihail.atanassov@arm.com>
> Signed-off-by: Liviu Dudau <liviu.dudau@arm.com>
> [rebased and added atomic_commit() vfunc for writeback jobs]
> Signed-off-by: Rob Clark <robdclark@gmail.com>
> ---
>  Documentation/gpu/drm-kms.rst            |   9 ++
>  drivers/gpu/drm/Makefile                 |   2 +-
>  drivers/gpu/drm/drm_atomic.c             | 130 ++++++++++++++++
>  drivers/gpu/drm/drm_atomic_helper.c      |  30 ++++
>  drivers/gpu/drm/drm_connector.c          |   4 +-
>  drivers/gpu/drm/drm_writeback.c          | 257 +++++++++++++++++++++++++++++++
>  include/drm/drm_atomic.h                 |   3 +
>  include/drm/drm_connector.h              |  13 ++
>  include/drm/drm_mode_config.h            |  14 ++
>  include/drm/drm_modeset_helper_vtables.h |  11 ++
>  include/drm/drm_writeback.h              |  89 +++++++++++
>  include/uapi/drm/drm_mode.h              |   1 +
>  12 files changed, 561 insertions(+), 2 deletions(-)
>  create mode 100644 drivers/gpu/drm/drm_writeback.c
>  create mode 100644 include/drm/drm_writeback.h
> 
> diff --git a/Documentation/gpu/drm-kms.rst b/Documentation/gpu/drm-kms.rst
> index 2dcf5b42015d..e7590dd2f71e 100644
> --- a/Documentation/gpu/drm-kms.rst
> +++ b/Documentation/gpu/drm-kms.rst
> @@ -370,6 +370,15 @@ Connector Functions Reference
>  .. kernel-doc:: drivers/gpu/drm/drm_connector.c
>     :export:
>  
> +Writeback Connectors
> +--------------------
> +
> +.. kernel-doc:: drivers/gpu/drm/drm_writeback.c
> +  :doc: overview
> +
> +.. kernel-doc:: drivers/gpu/drm/drm_writeback.c
> +  :export:
> +
>  Encoder Abstraction
>  ===================
>  
> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> index 50093ff4479b..3d708959b224 100644
> --- a/drivers/gpu/drm/Makefile
> +++ b/drivers/gpu/drm/Makefile
> @@ -18,7 +18,7 @@ drm-y       :=	drm_auth.o drm_bufs.o drm_cache.o \
>  		drm_encoder.o drm_mode_object.o drm_property.o \
>  		drm_plane.o drm_color_mgmt.o drm_print.o \
>  		drm_dumb_buffers.o drm_mode_config.o drm_vblank.o \
> -		drm_syncobj.o drm_lease.o
> +		drm_syncobj.o drm_lease.o drm_writeback.o
>  
>  drm-$(CONFIG_DRM_LIB_RANDOM) += lib/drm_random.o
>  drm-$(CONFIG_DRM_VM) += drm_vm.o
> diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
> index 46733d534587..019f131fe8be 100644
> --- a/drivers/gpu/drm/drm_atomic.c
> +++ b/drivers/gpu/drm/drm_atomic.c
> @@ -30,6 +30,7 @@
>  #include <drm/drm_atomic.h>
>  #include <drm/drm_mode.h>
>  #include <drm/drm_print.h>
> +#include <drm/drm_writeback.h>
>  #include <linux/sync_file.h>
>  
>  #include "drm_crtc_internal.h"
> @@ -638,6 +639,46 @@ static void drm_atomic_crtc_print_state(struct drm_printer *p,
>  		crtc->funcs->atomic_print_state(p, state);
>  }
>  
> +/**
> + * drm_atomic_connector_check - check connector state
> + * @connector: connector to check
> + * @state: connector state to check
> + *
> + * Provides core sanity checks for connector state.
> + *
> + * RETURNS:
> + * Zero on success, error code on failure
> + */
> +static int drm_atomic_connector_check(struct drm_connector *connector,
> +		struct drm_connector_state *state)
> +{
> +	struct drm_crtc_state *crtc_state;
> +	struct drm_writeback_job *writeback_job = state->writeback_job;
> +
> +	if ((connector->connector_type != DRM_MODE_CONNECTOR_WRITEBACK) ||
> +	    !writeback_job)
> +		return 0;
> +
> +	if (writeback_job->fb && !state->crtc) {
> +		DRM_DEBUG_ATOMIC("[CONNECTOR:%d:%s] framebuffer without CRTC\n",
> +				 connector->base.id, connector->name);
> +		return -EINVAL;
> +	}
> +
> +	if (state->crtc)
> +		crtc_state = drm_atomic_get_existing_crtc_state(state->state,
> +								state->crtc);
> +
> +	if (writeback_job->fb && !crtc_state->active) {

Checking for writeback_job->fb is redundant here and above since it's checked
first thing as early exit criteria.

> +		DRM_DEBUG_ATOMIC("[CONNECTOR:%d:%s] has framebuffer, but [CRTC:%d] is off\n",
> +				 connector->base.id, connector->name,
> +				 state->crtc->base.id);
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
>  /**
>   * drm_atomic_get_plane_state - get plane state
>   * @state: global atomic state object
> @@ -1230,6 +1271,12 @@ static int drm_atomic_connector_set_property(struct drm_connector *connector,
>  			return -EINVAL;
>  		}
>  		state->content_protection = val;
> +	} else if (property == config->writeback_fb_id_property) {
> +		struct drm_framebuffer *fb = drm_framebuffer_lookup(dev, NULL, val);
> +		int ret = drm_atomic_set_writeback_fb_for_connector(state, fb);
> +		if (fb)
> +			drm_framebuffer_unreference(fb);
> +		return ret;
>  	} else if (connector->funcs->atomic_set_property) {
>  		return connector->funcs->atomic_set_property(connector,
>  				state, property, val);
> @@ -1311,6 +1358,9 @@ drm_atomic_connector_get_property(struct drm_connector *connector,
>  		*val = state->scaling_mode;
>  	} else if (property == connector->content_protection_property) {
>  		*val = state->content_protection;
> +	} else if (property == config->writeback_fb_id_property) {
> +		/* Writeback framebuffer is one-shot, write and forget */
> +		*val = 0;
>  	} else if (connector->funcs->atomic_get_property) {
>  		return connector->funcs->atomic_get_property(connector,
>  				state, property, val);
> @@ -1518,6 +1568,75 @@ drm_atomic_set_crtc_for_connector(struct drm_connector_state *conn_state,
>  }
>  EXPORT_SYMBOL(drm_atomic_set_crtc_for_connector);
>  
> +/*
> + * drm_atomic_get_writeback_job - return or allocate a writeback job
> + * @conn_state: Connector state to get the job for
> + *
> + * Writeback jobs have a different lifetime to the atomic state they are
> + * associated with. This convenience function takes care of allocating a job
> + * if there isn't yet one associated with the connector state, otherwise
> + * it just returns the existing job.
> + *
> + * Returns: The writeback job for the given connector state
> + */
> +static struct drm_writeback_job *
> +drm_atomic_get_writeback_job(struct drm_connector_state *conn_state)
> +{
> +	WARN_ON(conn_state->connector->connector_type !=
> +		DRM_MODE_CONNECTOR_WRITEBACK);
> +
> +	if (!conn_state->writeback_job)
> +		conn_state->writeback_job =
> +			kzalloc(sizeof(*conn_state->writeback_job), GFP_KERNEL);
> +
> +	return conn_state->writeback_job;
> +}
> +
> +/**
> + * drm_atomic_set_writeback_fb_for_connector - set writeback framebuffer
> + * @conn_state: atomic state object for the connector
> + * @fb: fb to use for the connector
> + *
> + * This is used to set the framebuffer for a writeback connector, which outputs
> + * to a buffer instead of an actual physical connector.
> + * Changing the assigned framebuffer requires us to grab a reference to the new
> + * fb and drop the reference to the old fb, if there is one. This function
> + * takes care of all these details besides updating the pointer in the
> + * state object itself.
> + *
> + * Note: The only way conn_state can already have an fb set is if the commit
> + * sets the property more than once.
> + *
> + * See also: drm_writeback_connector_init()
> + *
> + * Returns: 0 on success
> + */
> +int drm_atomic_set_writeback_fb_for_connector(
> +		struct drm_connector_state *conn_state,
> +		struct drm_framebuffer *fb)
> +{
> +	struct drm_writeback_job *job =
> +		drm_atomic_get_writeback_job(conn_state);
> +	if (!job)
> +		return -ENOMEM;
> +
> +	if (job->fb)
> +		drm_framebuffer_unreference(job->fb);
> +	if (fb)
> +		drm_framebuffer_reference(fb);
> +	job->fb = fb;
> +
> +	if (fb)
> +		DRM_DEBUG_ATOMIC("Set [FB:%d] for connector state %p\n",
> +				 fb->base.id, conn_state);
> +	else
> +		DRM_DEBUG_ATOMIC("Set [NOFB] for connector state %p\n",
> +				 conn_state);
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL(drm_atomic_set_writeback_fb_for_connector);
> +
>  /**
>   * drm_atomic_add_affected_connectors - add connectors for crtc
>   * @state: atomic state
> @@ -1636,6 +1755,8 @@ int drm_atomic_check_only(struct drm_atomic_state *state)
>  	struct drm_plane_state *plane_state;
>  	struct drm_crtc *crtc;
>  	struct drm_crtc_state *crtc_state;
> +	struct drm_connector *conn;
> +	struct drm_connector_state *conn_state;
>  	int i, ret = 0;
>  
>  	DRM_DEBUG_ATOMIC("checking %p\n", state);
> @@ -1658,6 +1779,15 @@ int drm_atomic_check_only(struct drm_atomic_state *state)
>  		}
>  	}
>  
> +	for_each_new_connector_in_state(state, conn, conn_state, i) {
> +		ret = drm_atomic_connector_check(conn, conn_state);
> +		if (ret) {
> +			DRM_DEBUG_ATOMIC("[CONNECTOR:%d:%s] atomic core check failed\n",
> +					 conn->base.id, conn->name);
> +			return ret;
> +		}
> +	}
> +
>  	if (config->funcs->atomic_check)
>  		ret = config->funcs->atomic_check(state->dev, state);
>  
> diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c
> index ae3cbfe9e01c..12b910755d84 100644
> --- a/drivers/gpu/drm/drm_atomic_helper.c
> +++ b/drivers/gpu/drm/drm_atomic_helper.c
> @@ -30,6 +30,7 @@
>  #include <drm/drm_plane_helper.h>
>  #include <drm/drm_crtc_helper.h>
>  #include <drm/drm_atomic_helper.h>
> +#include <drm/drm_writeback.h>
>  #include <linux/dma-fence.h>
>  
>  #include "drm_crtc_helper_internal.h"
> @@ -1159,6 +1160,27 @@ void drm_atomic_helper_commit_modeset_disables(struct drm_device *dev,
>  }
>  EXPORT_SYMBOL(drm_atomic_helper_commit_modeset_disables);
>  
> +static void commit_writebacks(struct drm_device *dev, struct drm_atomic_state *old_state)
> +{
> +	struct drm_connector *connector;
> +	struct drm_connector_state *new_conn_state;
> +	int i;
> +
> +	for_each_new_connector_in_state(old_state, connector, new_conn_state, i) {
> +		const struct drm_connector_helper_funcs *funcs;
> +
> +		funcs = connector->helper_private;
> +
> +		if (new_conn_state->writeback_job &&
> +		    new_conn_state->writeback_job->fb) {
> +			WARN_ON(connector->connector_type !=
> +				DRM_MODE_CONNECTOR_WRITEBACK);
> +			funcs->atomic_commit(connector,
> +					     new_conn_state->writeback_job);
> +		}
> +	}
> +}
> +
>  /**
>   * drm_atomic_helper_commit_modeset_enables - modeset commit to enable outputs
>   * @dev: DRM device
> @@ -1238,6 +1260,8 @@ void drm_atomic_helper_commit_modeset_enables(struct drm_device *dev,
>  
>  		drm_bridge_enable(encoder->bridge);
>  	}
> +
> +	commit_writebacks(dev, old_state);
>  }
>  EXPORT_SYMBOL(drm_atomic_helper_commit_modeset_enables);
>  
> @@ -3627,6 +3651,9 @@ __drm_atomic_helper_connector_duplicate_state(struct drm_connector *connector,
>  	if (state->crtc)
>  		drm_connector_get(connector);
>  	state->commit = NULL;
> +
> +	/* Don't copy over a writeback job, they are used only once */
> +	state->writeback_job = NULL;
>  }
>  EXPORT_SYMBOL(__drm_atomic_helper_connector_duplicate_state);
>  
> @@ -3756,6 +3783,9 @@ __drm_atomic_helper_connector_destroy_state(struct drm_connector_state *state)
>  
>  	if (state->commit)
>  		drm_crtc_commit_put(state->commit);
> +
> +	if (state->writeback_job)
> +		drm_writeback_cleanup_job(state->writeback_job);
>  }
>  EXPORT_SYMBOL(__drm_atomic_helper_connector_destroy_state);
>  
> diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c
> index 16b9c3810af2..add47f06ae70 100644
> --- a/drivers/gpu/drm/drm_connector.c
> +++ b/drivers/gpu/drm/drm_connector.c
> @@ -87,6 +87,7 @@ static struct drm_conn_prop_enum_list drm_connector_enum_list[] = {
>  	{ DRM_MODE_CONNECTOR_VIRTUAL, "Virtual" },
>  	{ DRM_MODE_CONNECTOR_DSI, "DSI" },
>  	{ DRM_MODE_CONNECTOR_DPI, "DPI" },
> +	{ DRM_MODE_CONNECTOR_WRITEBACK, "Writeback" },
>  };
>  
>  void drm_connector_ida_init(void)
> @@ -249,7 +250,8 @@ int drm_connector_init(struct drm_device *dev,
>  	config->num_connector++;
>  	spin_unlock_irq(&config->connector_list_lock);
>  
> -	if (connector_type != DRM_MODE_CONNECTOR_VIRTUAL)
> +	if (connector_type != DRM_MODE_CONNECTOR_VIRTUAL &&
> +	    connector_type != DRM_MODE_CONNECTOR_WRITEBACK)
>  		drm_object_attach_property(&connector->base,
>  					      config->edid_property,
>  					      0);
> diff --git a/drivers/gpu/drm/drm_writeback.c b/drivers/gpu/drm/drm_writeback.c
> new file mode 100644
> index 000000000000..da61f929cbc3
> --- /dev/null
> +++ b/drivers/gpu/drm/drm_writeback.c
> @@ -0,0 +1,257 @@
> +/*
> + * (C) COPYRIGHT 2016 ARM Limited. All rights reserved.
> + * Author: Brian Starkey <brian.starkey@arm.com>
> + *
> + * This program is free software and is provided to you under the terms of the
> + * GNU General Public License version 2 as published by the Free Software
> + * Foundation, and any use by you of this program is subject to the terms
> + * of such GNU licence.
> + */
> +
> +#include <drm/drm_crtc.h>
> +#include <drm/drm_modeset_helper_vtables.h>
> +#include <drm/drm_property.h>
> +#include <drm/drm_writeback.h>
> +#include <drm/drmP.h>
> +
> +/**
> + * DOC: overview
> + *
> + * Writeback connectors are used to expose hardware which can write the output
> + * from a CRTC to a memory buffer. They are used and act similarly to other
> + * types of connectors, with some important differences:
> + *  - Writeback connectors don't provide a way to output visually to the user.
> + *  - Writeback connectors should always report as "disconnected" (so that
> + *    clients which don't understand them will ignore them).

Have we considered hiding writeback behind a client cap instead?

> + *  - Writeback connectors don't have EDID.
> + *
> + * A framebuffer may only be attached to a writeback connector when the
> + * connector is attached to a CRTC. The WRITEBACK_FB_ID property which sets the
> + * framebuffer applies only to a single commit (see below). A framebuffer may
> + * not be attached while the CRTC is off.
> + *
> + * Writeback connectors have some additional properties, which userspace
> + * can use to query and control them:
> + *
> + *  "WRITEBACK_FB_ID":
> + *	Write-only object property storing a DRM_MODE_OBJECT_FB: it stores the
> + *	framebuffer to be written by the writeback connector. This property is
> + *	similar to the FB_ID property on planes, but will always read as zero
> + *	and is not preserved across commits.
> + *	Userspace must set this property to an output buffer every time it
> + *	wishes the buffer to get filled.
> + *
> + *  "WRITEBACK_PIXEL_FORMATS":
> + *	Immutable blob property to store the supported pixel formats table. The
> + *	data is an array of u32 DRM_FORMAT_* fourcc values.
> + *	Userspace can use this blob to find out what pixel formats are supported
> + *	by the connector's writeback engine.
> + */
> +
> +static bool create_writeback_properties(struct drm_device *dev)
> +{
> +	struct drm_property *prop;
> +
> +	if (!dev->mode_config.writeback_fb_id_property) {

Somewhat an aside, but we should probably come up with more formal rules on
where to stick properties. We have some in drm_connector, which I interpret as
"connector-only properties", but we also have a bunch in mode_config.

For optional properties which will generally only apply to one connector on a
system, I'd be inclined to put it in connector. 

> +		prop = drm_property_create_object(dev, DRM_MODE_PROP_ATOMIC,
> +						  "WRITEBACK_FB_ID",
> +						  DRM_MODE_OBJECT_FB);
> +		if (!prop)
> +			return false;
> +		dev->mode_config.writeback_fb_id_property = prop;
> +	}
> +
> +	if (!dev->mode_config.writeback_pixel_formats_property) {
> +		prop = drm_property_create(dev, DRM_MODE_PROP_BLOB | DRM_MODE_PROP_IMMUTABLE,
> +					   "WRITEBACK_PIXEL_FORMATS", 0);

Does it make sense to expose the formats if u/s doesn't support atomic and can't
use writeback? Perhaps this should also be DRM_MODE_PROP_ATOMIC.


> +		if (!prop)
> +			return false;
> +		dev->mode_config.writeback_pixel_formats_property = prop;
> +	}
> +
> +	return true;
> +}
> +
> +static const struct drm_encoder_funcs drm_writeback_encoder_funcs = {
> +	.destroy = drm_encoder_cleanup,
> +};
> +
> +/**
> + * drm_writeback_connector_init - Initialize a writeback connector and its properties
> + * @dev: DRM device
> + * @wb_connector: Writeback connector to initialize
> + * @con_funcs: Connector funcs vtable
> + * @enc_helper_funcs: Encoder helper funcs vtable to be used by the internal encoder
> + * @formats: Array of supported pixel formats for the writeback engine
> + * @n_formats: Length of the formats array
> + *
> + * This function creates the writeback-connector-specific properties if they
> + * have not been already created, initializes the connector as
> + * type DRM_MODE_CONNECTOR_WRITEBACK, and correctly initializes the property
> + * values. It will also create an internal encoder associated with the
> + * drm_writeback_connector and set it to use the @enc_helper_funcs vtable for
> + * the encoder helper.
> + *
> + * Drivers should always use this function instead of drm_connector_init() to
> + * set up writeback connectors.
> + *
> + * Returns: 0 on success, or a negative error code
> + */
> +int drm_writeback_connector_init(struct drm_device *dev,
> +				 struct drm_writeback_connector *wb_connector,
> +				 const struct drm_connector_funcs *con_funcs,
> +				 const struct drm_encoder_helper_funcs *enc_helper_funcs,
> +				 const u32 *formats, int n_formats)
> +{
> +	int ret;
> +	struct drm_property_blob *blob;
> +	struct drm_connector *connector = &wb_connector->base;
> +	struct drm_mode_config *config = &dev->mode_config;
> +
> +	if (!create_writeback_properties(dev))
> +		return -EINVAL;
> +
> +	blob = drm_property_create_blob(dev, n_formats * sizeof(*formats),
> +					formats);
> +	if (IS_ERR(blob))
> +		return PTR_ERR(blob);
> +
> +	drm_encoder_helper_add(&wb_connector->encoder, enc_helper_funcs);
> +	ret = drm_encoder_init(dev, &wb_connector->encoder,
> +			       &drm_writeback_encoder_funcs,
> +			       DRM_MODE_ENCODER_VIRTUAL, NULL);
> +	if (ret)
> +		goto fail;
> +
> +	connector->interlace_allowed = 0;
> +
> +	ret = drm_connector_init(dev, connector, con_funcs,
> +				 DRM_MODE_CONNECTOR_WRITEBACK);
> +	if (ret)
> +		goto connector_fail;
> +
> +	ret = drm_mode_connector_attach_encoder(connector,
> +						&wb_connector->encoder);
> +	if (ret)
> +		goto attach_fail;
> +
> +	INIT_LIST_HEAD(&wb_connector->job_queue);
> +	spin_lock_init(&wb_connector->job_lock);
> +
> +	drm_object_attach_property(&connector->base,
> +				   config->writeback_fb_id_property, 0);
> +
> +	drm_object_attach_property(&connector->base,
> +				   config->writeback_pixel_formats_property,
> +				   blob->base.id);
> +	wb_connector->pixel_formats_blob_ptr = blob;
> +
> +	return 0;
> +
> +attach_fail:
> +	drm_connector_cleanup(connector);
> +connector_fail:
> +	drm_encoder_cleanup(&wb_connector->encoder);
> +fail:
> +	drm_property_unreference_blob(blob);
> +	return ret;
> +}
> +EXPORT_SYMBOL(drm_writeback_connector_init);
> +
> +/**
> + * drm_writeback_queue_job - Queue a writeback job for later signalling
> + * @wb_connector: The writeback connector to queue a job on
> + * @job: The job to queue
> + *
> + * This function adds a job to the job_queue for a writeback connector. It
> + * should be considered to take ownership of the writeback job, and so any other
> + * references to the job must be cleared after calling this function.
> + *
> + * Drivers must ensure that for a given writeback connector, jobs are queued in
> + * exactly the same order as they will be completed by the hardware (and
> + * signaled via drm_writeback_signal_completion).
> + *
> + * For every call to drm_writeback_queue_job() there must be exactly one call to
> + * drm_writeback_signal_completion()
> + *
> + * See also: drm_writeback_signal_completion()
> + */
> +void drm_writeback_queue_job(struct drm_writeback_connector *wb_connector,
> +			     struct drm_writeback_job *job)
> +{
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&wb_connector->job_lock, flags);
> +	list_add_tail(&job->list_entry, &wb_connector->job_queue);
> +	spin_unlock_irqrestore(&wb_connector->job_lock, flags);
> +}
> +EXPORT_SYMBOL(drm_writeback_queue_job);
> +
> +/**
> + * drm_writeback_cleanup_job - Cleanup and free a writeback job
> + * @job: The writeback job to free
> + *
> + * Drops any references held by the writeback job, and frees the structure.
> + */
> +void drm_writeback_cleanup_job(struct drm_writeback_job *job)
> +{
> +	if (!job)
> +		return;
> +
> +	if (job->fb)
> +		drm_framebuffer_unreference(job->fb);
> +	kfree(job);
> +}
> +EXPORT_SYMBOL(drm_writeback_cleanup_job);
> +
> +/*
> + * @cleanup_work: deferred cleanup of a writeback job
> + *
> + * The job cannot be cleaned up directly in drm_writeback_signal_completion,
> + * because it may be called in interrupt context. Dropping the framebuffer
> + * reference can sleep, and so the cleanup is deferred to a workqueue.
> + */
> +static void cleanup_work(struct work_struct *work)
> +{
> +	struct drm_writeback_job *job = container_of(work,
> +						     struct drm_writeback_job,
> +						     cleanup_work);
> +	drm_writeback_cleanup_job(job);

TIL that you can free work while using it.

> +}
> +
> +/**
> + * drm_writeback_signal_completion - Signal the completion of a writeback job
> + * @wb_connector: The writeback connector whose job is complete
> + *
> + * Drivers should call this to signal the completion of a previously queued
> + * writeback job. It should be called as soon as possible after the hardware
> + * has finished writing, and may be called from interrupt context.
> + * It is the driver's responsibility to ensure that for a given connector, the
> + * hardware completes writeback jobs in the same order as they are queued.
> + *
> + * Unless the driver is holding its own reference to the framebuffer, it must
> + * not be accessed after calling this function.
> + *
> + * See also: drm_writeback_queue_job()
> + */
> +void
> +drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector)
> +{
> +	unsigned long flags;
> +	struct drm_writeback_job *job;
> +
> +	spin_lock_irqsave(&wb_connector->job_lock, flags);
> +	job = list_first_entry_or_null(&wb_connector->job_queue,
> +				       struct drm_writeback_job,
> +				       list_entry);
> +	if (job)
> +		list_del(&job->list_entry);
> +	spin_unlock_irqrestore(&wb_connector->job_lock, flags);
> +
> +	if (WARN_ON(!job))
> +		return;
> +
> +	INIT_WORK(&job->cleanup_work, cleanup_work);
> +	queue_work(system_long_wq, &job->cleanup_work);
> +}
> +EXPORT_SYMBOL(drm_writeback_signal_completion);
> diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
> index cf13842a6dbd..d7b0263cc5cf 100644
> --- a/include/drm/drm_atomic.h
> +++ b/include/drm/drm_atomic.h
> @@ -594,6 +594,9 @@ void drm_atomic_set_fence_for_plane(struct drm_plane_state *plane_state,
>  int __must_check
>  drm_atomic_set_crtc_for_connector(struct drm_connector_state *conn_state,
>  				  struct drm_crtc *crtc);
> +int drm_atomic_set_writeback_fb_for_connector(
> +		struct drm_connector_state *conn_state,
> +		struct drm_framebuffer *fb);
>  int __must_check
>  drm_atomic_add_affected_connectors(struct drm_atomic_state *state,
>  				   struct drm_crtc *crtc);
> diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
> index 758a176e7b57..8701ebcc68b3 100644
> --- a/include/drm/drm_connector.h
> +++ b/include/drm/drm_connector.h
> @@ -425,6 +425,19 @@ struct drm_connector_state {
>  	 * protection. This is most commonly used for HDCP.
>  	 */
>  	unsigned int content_protection;
> +
> +	/**
> +	 * @writeback_job: Writeback job for writeback connectors
> +	 *
> +	 * Holds the framebuffer for a writeback connector. As the writeback
> +	 * completion may be asynchronous to the normal commit cycle, the
> +	 * writeback job lifetime is managed separately from the normal atomic
> +	 * state by this object.
> +	 *
> +	 * See also: drm_writeback_queue_job() and
> +	 * drm_writeback_signal_completion()
> +	 */
> +	struct drm_writeback_job *writeback_job;
>  };
>  
>  /**
> diff --git a/include/drm/drm_mode_config.h b/include/drm/drm_mode_config.h
> index 7569f22ffef6..c012e1148ec0 100644
> --- a/include/drm/drm_mode_config.h
> +++ b/include/drm/drm_mode_config.h
> @@ -779,6 +779,20 @@ struct drm_mode_config {
>  	 */
>  	struct drm_property *panel_orientation_property;
>  
> +	/**
> +	 * @writeback_fb_id_property: Property for writeback connectors, storing
> +	 * the ID of the output framebuffer.
> +	 * See also: drm_writeback_connector_init()
> +	 */
> +	struct drm_property *writeback_fb_id_property;
> +	/**
> +	 * @writeback_pixel_formats_property: Property for writeback connectors,
> +	 * storing an array of the supported pixel formats for the writeback
> +	 * engine (read-only).
> +	 * See also: drm_writeback_connector_init()
> +	 */
> +	struct drm_property *writeback_pixel_formats_property;
> +
>  	/* dumb ioctl parameters */
>  	uint32_t preferred_depth, prefer_shadow;
>  
> diff --git a/include/drm/drm_modeset_helper_vtables.h b/include/drm/drm_modeset_helper_vtables.h
> index 3e76ca805b0f..97d3a810bc85 100644
> --- a/include/drm/drm_modeset_helper_vtables.h
> +++ b/include/drm/drm_modeset_helper_vtables.h
> @@ -974,6 +974,17 @@ struct drm_connector_helper_funcs {
>  	 */
>  	int (*atomic_check)(struct drm_connector *connector,
>  			    struct drm_connector_state *state);
> +
> +	/**
> +	 * @atomic_commit:
> +	 *
> +	 * For write-back connectors, this is the point to commit the write-
> +	 * back job to hw.
> +	 *
> +	 * This callback is used by the atomic modeset helpers.
> +	 */
> +	void (*atomic_commit)(struct drm_connector *connector,
> +			      struct drm_writeback_job *writeback_job);
>  };
>  
>  /**
> diff --git a/include/drm/drm_writeback.h b/include/drm/drm_writeback.h
> new file mode 100644
> index 000000000000..0bb95fd4907d
> --- /dev/null
> +++ b/include/drm/drm_writeback.h
> @@ -0,0 +1,89 @@
> +/*
> + * (C) COPYRIGHT 2016 ARM Limited. All rights reserved.
> + * Author: Brian Starkey <brian.starkey@arm.com>
> + *
> + * This program is free software and is provided to you under the terms of the
> + * GNU General Public License version 2 as published by the Free Software
> + * Foundation, and any use by you of this program is subject to the terms
> + * of such GNU licence.
> + */
> +
> +#ifndef __DRM_WRITEBACK_H__
> +#define __DRM_WRITEBACK_H__
> +#include <drm/drm_connector.h>
> +#include <drm/drm_encoder.h>
> +#include <linux/workqueue.h>
> +
> +struct drm_writeback_connector {
> +	struct drm_connector base;
> +
> +	/**
> +	 * @encoder: Internal encoder used by the connector to fulfill
> +	 * the DRM framework requirements. The users of the
> +	 * @drm_writeback_connector control the behaviour of the @encoder
> +	 * by passing the @enc_funcs parameter to drm_writeback_connector_init()
> +	 * function.
> +	 */
> +	struct drm_encoder encoder;
> +
> +	/**
> +	 * @pixel_formats_blob_ptr:
> +	 *
> +	 * DRM blob property data for the pixel formats list on writeback
> +	 * connectors
> +	 * See also drm_writeback_connector_init()
> +	 */
> +	struct drm_property_blob *pixel_formats_blob_ptr;
> +
> +	/** @job_lock: Protects job_queue */
> +	spinlock_t job_lock;
> +
> +	/**
> +	 * @job_queue:
> +	 *
> +	 * Holds a list of a connector's writeback jobs; the last item is the
> +	 * most recent. The first item may be either waiting for the hardware
> +	 * to begin writing, or currently being written.
> +	 *
> +	 * See also: drm_writeback_queue_job() and
> +	 * drm_writeback_signal_completion()
> +	 */
> +	struct list_head job_queue;
> +};
> +#define to_wb_connector(x) container_of(x, struct drm_writeback_connector, base)
> +
> +struct drm_writeback_job {
> +	/**
> +	 * @cleanup_work:
> +	 *
> +	 * Used to allow drm_writeback_signal_completion to defer dropping the
> +	 * framebuffer reference to a workqueue.
> +	 */
> +	struct work_struct cleanup_work;
> +	/**
> +	 * @list_entry:
> +	 *
> +	 * List item for the connector's @job_queue
> +	 */
> +	struct list_head list_entry;
> +	/**
> +	 * @fb:
> +	 *
> +	 * Framebuffer to be written to by the writeback connector. Do not set
> +	 * directly, use drm_atomic_set_writeback_fb_for_connector()
> +	 */
> +	struct drm_framebuffer *fb;
> +};
> +
> +int drm_writeback_connector_init(struct drm_device *dev,
> +				 struct drm_writeback_connector *wb_connector,
> +				 const struct drm_connector_funcs *con_funcs,
> +				 const struct drm_encoder_helper_funcs *enc_helper_funcs,
> +				 const u32 *formats, int n_formats);
> +
> +void drm_writeback_queue_job(struct drm_writeback_connector *wb_connector,
> +			     struct drm_writeback_job *job);
> +
> +void drm_writeback_cleanup_job(struct drm_writeback_job *job);
> +void drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector);
> +#endif
> diff --git a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h
> index 2c575794fb52..7b47e184e95e 100644
> --- a/include/uapi/drm/drm_mode.h
> +++ b/include/uapi/drm/drm_mode.h
> @@ -338,6 +338,7 @@ enum drm_mode_subconnector {
>  #define DRM_MODE_CONNECTOR_VIRTUAL      15
>  #define DRM_MODE_CONNECTOR_DSI		16
>  #define DRM_MODE_CONNECTOR_DPI		17
> +#define DRM_MODE_CONNECTOR_WRITEBACK	18
>  
>  struct drm_mode_get_connector {
>  
> -- 
> 2.14.3
> 

-- 
Sean Paul, Software Engineer, Google / Chromium OS

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [RFC 2/4] drm: writeback: Add out-fences for writeback connectors
  2018-02-23 13:17     ` Rob Clark
@ 2018-02-23 16:14         ` Sean Paul
  -1 siblings, 0 replies; 41+ messages in thread
From: Sean Paul @ 2018-02-23 16:14 UTC (permalink / raw)
  To: Rob Clark
  Cc: Maarten Lankhorst, linux-arm-msm-u79uwXL29TY76Z2rM5mHXA,
	Liviu Dudau, linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, David Airlie,
	Sean Paul, Gustavo Padovan, Mihail Atanassov,
	freedreno-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, Brian Starkey

On Fri, Feb 23, 2018 at 08:17:52AM -0500, Rob Clark wrote:
> From: Brian Starkey <brian.starkey@arm.com>
> 
> Add the WRITEBACK_OUT_FENCE_PTR property to writeback connectors, to
> enable userspace to get a fence which will signal once the writeback is
> complete. It is not allowed to request an out-fence without a
> framebuffer attached to the connector.
> 
> A timeline is added to drm_writeback_connector for use by the writeback
> out-fences.
> 
> In the case of a commit failure or DRM_MODE_ATOMIC_TEST_ONLY, the fence
> is set to -1.
> 
> Changes from v2:
>  - Rebase onto Gustavo Padovan's v9 explicit sync series
>  - Change out_fence_ptr type to s32 __user *
>  - Set *out_fence_ptr to -1 in drm_atomic_connector_set_property
>  - Store fence in drm_writeback_job
>  Gustavo Padovan:
>  - Move out_fence_ptr out of connector_state
>  - Signal fence from drm_writeback_signal_completion instead of
>    in driver directly
> 
> Changes from v3:
>  - Rebase onto 7e9081c5aac7 drm/fence: fix memory overwrite when setting out_fence fd
>    (change out_fence_ptr to s32 __user *, for real this time.)
>  - Update documentation around WRITEBACK_OUT_FENCE_PTR
> 
> Signed-off-by: Brian Starkey <brian.starkey@arm.com>
> [rebased and fixed conflicts]
> Signed-off-by: Mihail Atanassov <mihail.atanassov@arm.com>
> Signed-off-by: Liviu Dudau <liviu.dudau@arm.com>
> Signed-off-by: Rob Clark <robdclark@gmail.com>

Reviewed-by: Sean Paul <seanpaul@chromium.org>

> ---
>  drivers/gpu/drm/drm_atomic.c    |  99 ++++++++++++++++++++++++++++++++----
>  drivers/gpu/drm/drm_writeback.c | 109 +++++++++++++++++++++++++++++++++++++++-
>  include/drm/drm_atomic.h        |   8 +++
>  include/drm/drm_connector.h     |   8 +--
>  include/drm/drm_mode_config.h   |   8 +++
>  include/drm/drm_writeback.h     |  41 ++++++++++++++-
>  6 files changed, 257 insertions(+), 16 deletions(-)
> 
> diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
> index 019f131fe8be..fc8c4da409ff 100644
> --- a/drivers/gpu/drm/drm_atomic.c
> +++ b/drivers/gpu/drm/drm_atomic.c
> @@ -318,6 +318,35 @@ static s32 __user *get_out_fence_for_crtc(struct drm_atomic_state *state,
>  	return fence_ptr;
>  }
>  
> +static int set_out_fence_for_connector(struct drm_atomic_state *state,
> +					struct drm_connector *connector,
> +					s32 __user *fence_ptr)
> +{
> +	unsigned int index = drm_connector_index(connector);
> +
> +	if (!fence_ptr)
> +		return 0;
> +
> +	if (put_user(-1, fence_ptr))
> +		return -EFAULT;
> +
> +	state->connectors[index].out_fence_ptr = fence_ptr;
> +
> +	return 0;
> +}
> +
> +static s32 __user *get_out_fence_for_connector(struct drm_atomic_state *state,
> +					       struct drm_connector *connector)
> +{
> +	unsigned int index = drm_connector_index(connector);
> +	s32 __user *fence_ptr;
> +
> +	fence_ptr = state->connectors[index].out_fence_ptr;
> +	state->connectors[index].out_fence_ptr = NULL;
> +
> +	return fence_ptr;
> +}
> +
>  /**
>   * drm_atomic_set_mode_for_crtc - set mode for CRTC
>   * @state: the CRTC whose incoming state to update
> @@ -676,6 +705,12 @@ static int drm_atomic_connector_check(struct drm_connector *connector,
>  		return -EINVAL;
>  	}
>  
> +	if (writeback_job->out_fence && !writeback_job->fb) {
> +		DRM_DEBUG_ATOMIC("[CONNECTOR:%d:%s] requesting out-fence without framebuffer\n",
> +				 connector->base.id, connector->name);
> +		return -EINVAL;
> +	}
> +
>  	return 0;
>  }
>  
> @@ -1277,6 +1312,11 @@ static int drm_atomic_connector_set_property(struct drm_connector *connector,
>  		if (fb)
>  			drm_framebuffer_unreference(fb);
>  		return ret;
> +	} else if (property == config->writeback_out_fence_ptr_property) {
> +		s32 __user *fence_ptr = u64_to_user_ptr(val);
> +
> +		return set_out_fence_for_connector(state->state, connector,
> +						   fence_ptr);
>  	} else if (connector->funcs->atomic_set_property) {
>  		return connector->funcs->atomic_set_property(connector,
>  				state, property, val);
> @@ -1361,6 +1401,8 @@ drm_atomic_connector_get_property(struct drm_connector *connector,
>  	} else if (property == config->writeback_fb_id_property) {
>  		/* Writeback framebuffer is one-shot, write and forget */
>  		*val = 0;
> +	} else if (property == config->writeback_out_fence_ptr_property) {
> +		*val = 0;
>  	} else if (connector->funcs->atomic_get_property) {
>  		return connector->funcs->atomic_get_property(connector,
>  				state, property, val);
> @@ -2221,7 +2263,7 @@ static int setup_out_fence(struct drm_out_fence_state *fence_state,
>  	return 0;
>  }
>  
> -static int prepare_crtc_signaling(struct drm_device *dev,
> +static int prepare_signaling(struct drm_device *dev,
>  				  struct drm_atomic_state *state,
>  				  struct drm_mode_atomic *arg,
>  				  struct drm_file *file_priv,
> @@ -2230,6 +2272,8 @@ static int prepare_crtc_signaling(struct drm_device *dev,
>  {
>  	struct drm_crtc *crtc;
>  	struct drm_crtc_state *crtc_state;
> +	struct drm_connector *conn;
> +	struct drm_connector_state *conn_state;
>  	int i, c = 0, ret;
>  
>  	if (arg->flags & DRM_MODE_ATOMIC_TEST_ONLY)
> @@ -2295,6 +2339,43 @@ static int prepare_crtc_signaling(struct drm_device *dev,
>  		c++;
>  	}
>  
> +	for_each_new_connector_in_state(state, conn, conn_state, i) {
> +		struct drm_writeback_job *job;
> +		struct drm_out_fence_state *f;
> +		struct dma_fence *fence;
> +		s32 __user *fence_ptr;
> +
> +		fence_ptr = get_out_fence_for_connector(state, conn);
> +		if (!fence_ptr)
> +			continue;
> +
> +		job = drm_atomic_get_writeback_job(conn_state);
> +		if (!job)
> +			return -ENOMEM;
> +
> +		f = krealloc(*fence_state, sizeof(**fence_state) *
> +			     (*num_fences + 1), GFP_KERNEL);
> +		if (!f)
> +			return -ENOMEM;
> +
> +		memset(&f[*num_fences], 0, sizeof(*f));
> +
> +		f[*num_fences].out_fence_ptr = fence_ptr;
> +		*fence_state = f;
> +
> +		fence = drm_writeback_get_out_fence((struct drm_writeback_connector *)conn);
> +		if (!fence)
> +			return -ENOMEM;
> +
> +		ret = setup_out_fence(&f[(*num_fences)++], fence);
> +		if (ret) {
> +			dma_fence_put(fence);
> +			return ret;
> +		}
> +
> +		job->out_fence = fence;
> +	}
> +
>  	/*
>  	 * Having this flag means user mode pends on event which will never
>  	 * reach due to lack of at least one CRTC for signaling
> @@ -2305,11 +2386,11 @@ static int prepare_crtc_signaling(struct drm_device *dev,
>  	return 0;
>  }
>  
> -static void complete_crtc_signaling(struct drm_device *dev,
> -				    struct drm_atomic_state *state,
> -				    struct drm_out_fence_state *fence_state,
> -				    unsigned int num_fences,
> -				    bool install_fds)
> +static void complete_signaling(struct drm_device *dev,
> +			       struct drm_atomic_state *state,
> +			       struct drm_out_fence_state *fence_state,
> +			       unsigned int num_fences,
> +			       bool install_fds)
>  {
>  	struct drm_crtc *crtc;
>  	struct drm_crtc_state *crtc_state;
> @@ -2488,8 +2569,8 @@ int drm_mode_atomic_ioctl(struct drm_device *dev,
>  		drm_mode_object_put(obj);
>  	}
>  
> -	ret = prepare_crtc_signaling(dev, state, arg, file_priv, &fence_state,
> -				     &num_fences);
> +	ret = prepare_signaling(dev, state, arg, file_priv, &fence_state,
> +				&num_fences);
>  	if (ret)
>  		goto out;
>  
> @@ -2507,7 +2588,7 @@ int drm_mode_atomic_ioctl(struct drm_device *dev,
>  out:
>  	drm_atomic_clean_old_fb(dev, plane_mask, ret);
>  
> -	complete_crtc_signaling(dev, state, fence_state, num_fences, !ret);
> +	complete_signaling(dev, state, fence_state, num_fences, !ret);
>  
>  	if (ret == -EDEADLK) {
>  		drm_atomic_state_clear(state);
> diff --git a/drivers/gpu/drm/drm_writeback.c b/drivers/gpu/drm/drm_writeback.c
> index da61f929cbc3..839e79932c19 100644
> --- a/drivers/gpu/drm/drm_writeback.c
> +++ b/drivers/gpu/drm/drm_writeback.c
> @@ -13,6 +13,7 @@
>  #include <drm/drm_property.h>
>  #include <drm/drm_writeback.h>
>  #include <drm/drmP.h>
> +#include <linux/dma-fence.h>
>  
>  /**
>   * DOC: overview
> @@ -30,6 +31,16 @@
>   * framebuffer applies only to a single commit (see below). A framebuffer may
>   * not be attached while the CRTC is off.
>   *
> + * Unlike with planes, when a writeback framebuffer is removed by userspace DRM
> + * makes no attempt to remove it from active use by the connector. This is
> + * because no method is provided to abort a writeback operation, and in any
> + * case making a new commit whilst a writeback is ongoing is undefined (see
> + * WRITEBACK_OUT_FENCE_PTR below). As soon as the current writeback is finished,
> + * the framebuffer will automatically no longer be in active use. As it will
> + * also have already been removed from the framebuffer list, there will be no
> + * way for any userspace application to retrieve a reference to it in the
> + * intervening period.
> + *
>   * Writeback connectors have some additional properties, which userspace
>   * can use to query and control them:
>   *
> @@ -46,8 +57,54 @@
>   *	data is an array of u32 DRM_FORMAT_* fourcc values.
>   *	Userspace can use this blob to find out what pixel formats are supported
>   *	by the connector's writeback engine.
> + *
> + *  "WRITEBACK_OUT_FENCE_PTR":
> + *	Userspace can use this property to provide a pointer for the kernel to
> + *	fill with a sync_file file descriptor, which will signal once the
> + *	writeback is finished. The value should be the address of a 32-bit
> + *	signed integer, cast to a u64.
> + *	Userspace should wait for this fence to signal before making another
> + *	commit affecting any of the same CRTCs, Planes or Connectors.
> + *	**Failure to do so will result in undefined behaviour.**
> + *	For this reason it is strongly recommended that all userspace
> + *	applications making use of writeback connectors *always* retrieve an
> + *	out-fence for the commit and use it appropriately.
> + *	From userspace, this property will always read as zero.
>   */
>  
> +#define fence_to_wb_connector(x) container_of(x->lock, \
> +					      struct drm_writeback_connector, \
> +					      fence_lock)
> +
> +static const char *drm_writeback_fence_get_driver_name(struct dma_fence *fence)
> +{
> +	struct drm_writeback_connector *wb_connector =
> +		fence_to_wb_connector(fence);
> +
> +	return wb_connector->base.dev->driver->name;
> +}
> +
> +static const char *
> +drm_writeback_fence_get_timeline_name(struct dma_fence *fence)
> +{
> +	struct drm_writeback_connector *wb_connector =
> +		fence_to_wb_connector(fence);
> +
> +	return wb_connector->timeline_name;
> +}
> +
> +static bool drm_writeback_fence_enable_signaling(struct dma_fence *fence)
> +{
> +	return true;
> +}
> +
> +static const struct dma_fence_ops drm_writeback_fence_ops = {
> +	.get_driver_name = drm_writeback_fence_get_driver_name,
> +	.get_timeline_name = drm_writeback_fence_get_timeline_name,
> +	.enable_signaling = drm_writeback_fence_enable_signaling,
> +	.wait = dma_fence_default_wait,
> +};
> +
>  static bool create_writeback_properties(struct drm_device *dev)
>  {
>  	struct drm_property *prop;
> @@ -69,6 +126,15 @@ static bool create_writeback_properties(struct drm_device *dev)
>  		dev->mode_config.writeback_pixel_formats_property = prop;
>  	}
>  
> +	if (!dev->mode_config.writeback_out_fence_ptr_property) {
> +		prop = drm_property_create_range(dev, DRM_MODE_PROP_ATOMIC,
> +						 "WRITEBACK_OUT_FENCE_PTR", 0,
> +						 U64_MAX);
> +		if (!prop)
> +			return -ENOMEM;
> +		dev->mode_config.writeback_out_fence_ptr_property = prop;
> +	}
> +
>  	return true;
>  }
>  
> @@ -138,6 +204,15 @@ int drm_writeback_connector_init(struct drm_device *dev,
>  	INIT_LIST_HEAD(&wb_connector->job_queue);
>  	spin_lock_init(&wb_connector->job_lock);
>  
> +	wb_connector->fence_context = dma_fence_context_alloc(1);
> +	spin_lock_init(&wb_connector->fence_lock);
> +	snprintf(wb_connector->timeline_name,
> +		 sizeof(wb_connector->timeline_name),
> +		 "CONNECTOR:%d-%s", connector->base.id, connector->name);
> +
> +	drm_object_attach_property(&connector->base,
> +				   config->writeback_out_fence_ptr_property, 0);
> +
>  	drm_object_attach_property(&connector->base,
>  				   config->writeback_fb_id_property, 0);
>  
> @@ -200,6 +275,7 @@ void drm_writeback_cleanup_job(struct drm_writeback_job *job)
>  
>  	if (job->fb)
>  		drm_framebuffer_unreference(job->fb);
> +	dma_fence_put(job->out_fence);
>  	kfree(job);
>  }
>  EXPORT_SYMBOL(drm_writeback_cleanup_job);
> @@ -222,6 +298,7 @@ static void cleanup_work(struct work_struct *work)
>  /**
>   * drm_writeback_signal_completion - Signal the completion of a writeback job
>   * @wb_connector: The writeback connector whose job is complete
> + * @status: Status code to set in the writeback out_fence (0 for success)
>   *
>   * Drivers should call this to signal the completion of a previously queued
>   * writeback job. It should be called as soon as possible after the hardware
> @@ -235,7 +312,8 @@ static void cleanup_work(struct work_struct *work)
>   * See also: drm_writeback_queue_job()
>   */
>  void
> -drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector)
> +drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector,
> +				int status)
>  {
>  	unsigned long flags;
>  	struct drm_writeback_job *job;
> @@ -244,8 +322,14 @@ drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector)
>  	job = list_first_entry_or_null(&wb_connector->job_queue,
>  				       struct drm_writeback_job,
>  				       list_entry);
> -	if (job)
> +	if (job) {
>  		list_del(&job->list_entry);
> +		if (job->out_fence) {
> +			if (status)
> +				dma_fence_set_error(job->out_fence, status);
> +			dma_fence_signal(job->out_fence);
> +		}
> +	}
>  	spin_unlock_irqrestore(&wb_connector->job_lock, flags);
>  
>  	if (WARN_ON(!job))
> @@ -255,3 +339,24 @@ drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector)
>  	queue_work(system_long_wq, &job->cleanup_work);
>  }
>  EXPORT_SYMBOL(drm_writeback_signal_completion);
> +
> +struct dma_fence *
> +drm_writeback_get_out_fence(struct drm_writeback_connector *wb_connector)
> +{
> +	struct dma_fence *fence;
> +
> +	if (WARN_ON(wb_connector->base.connector_type !=
> +		    DRM_MODE_CONNECTOR_WRITEBACK))
> +		return NULL;
> +
> +	fence = kzalloc(sizeof(*fence), GFP_KERNEL);
> +	if (!fence)
> +		return NULL;
> +
> +	dma_fence_init(fence, &drm_writeback_fence_ops,
> +		       &wb_connector->fence_lock, wb_connector->fence_context,
> +		       ++wb_connector->fence_seqno);
> +
> +	return fence;
> +}
> +EXPORT_SYMBOL(drm_writeback_get_out_fence);
> diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
> index d7b0263cc5cf..09076a625637 100644
> --- a/include/drm/drm_atomic.h
> +++ b/include/drm/drm_atomic.h
> @@ -160,6 +160,14 @@ struct __drm_crtcs_state {
>  struct __drm_connnectors_state {
>  	struct drm_connector *ptr;
>  	struct drm_connector_state *state, *old_state, *new_state;
> +	/**
> +	 * @out_fence_ptr:
> +	 *
> +	 * User-provided pointer which the kernel uses to return a sync_file
> +	 * file descriptor. Used by writeback connectors to signal completion of
> +	 * the writeback.
> +	 */
> +	s32 __user *out_fence_ptr;
>  };
>  
>  struct drm_private_obj;
> diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
> index 8701ebcc68b3..da56fda6e218 100644
> --- a/include/drm/drm_connector.h
> +++ b/include/drm/drm_connector.h
> @@ -429,10 +429,10 @@ struct drm_connector_state {
>  	/**
>  	 * @writeback_job: Writeback job for writeback connectors
>  	 *
> -	 * Holds the framebuffer for a writeback connector. As the writeback
> -	 * completion may be asynchronous to the normal commit cycle, the
> -	 * writeback job lifetime is managed separately from the normal atomic
> -	 * state by this object.
> +	 * Holds the framebuffer and out-fence for a writeback connector. As
> +	 * the writeback completion may be asynchronous to the normal commit
> +	 * cycle, the writeback job lifetime is managed separately from the
> +	 * normal atomic state by this object.
>  	 *
>  	 * See also: drm_writeback_queue_job() and
>  	 * drm_writeback_signal_completion()
> diff --git a/include/drm/drm_mode_config.h b/include/drm/drm_mode_config.h
> index c012e1148ec0..930e00560380 100644
> --- a/include/drm/drm_mode_config.h
> +++ b/include/drm/drm_mode_config.h
> @@ -792,6 +792,14 @@ struct drm_mode_config {
>  	 * See also: drm_writeback_connector_init()
>  	 */
>  	struct drm_property *writeback_pixel_formats_property;
> +	/**
> +	 * @writeback_out_fence_ptr_property: Property for writeback connectors,
> +	 * fd pointer representing the outgoing fences for a writeback
> +	 * connector. Userspace should provide a pointer to a value of type s32,
> +	 * and then cast that pointer to u64.
> +	 * See also: drm_writeback_connector_init()
> +	 */
> +	struct drm_property *writeback_out_fence_ptr_property;
>  
>  	/* dumb ioctl parameters */
>  	uint32_t preferred_depth, prefer_shadow;
> diff --git a/include/drm/drm_writeback.h b/include/drm/drm_writeback.h
> index 0bb95fd4907d..b9470ae2a53d 100644
> --- a/include/drm/drm_writeback.h
> +++ b/include/drm/drm_writeback.h
> @@ -49,6 +49,32 @@ struct drm_writeback_connector {
>  	 * drm_writeback_signal_completion()
>  	 */
>  	struct list_head job_queue;
> +
> +	/**
> +	 * @fence_context:
> +	 *
> +	 * timeline context used for fence operations.
> +	 */
> +	unsigned int fence_context;
> +	/**
> +	 * @fence_lock:
> +	 *
> +	 * spinlock to protect the fences in the fence_context.
> +	 */
> +	spinlock_t fence_lock;
> +	/**
> +	 * @fence_seqno:
> +	 *
> +	 * Seqno variable used as monotonic counter for the fences
> +	 * created on the connector's timeline.
> +	 */
> +	unsigned long fence_seqno;
> +	/**
> +	 * @timeline_name:
> +	 *
> +	 * The name of the connector's fence timeline.
> +	 */
> +	char timeline_name[32];
>  };
>  #define to_wb_connector(x) container_of(x, struct drm_writeback_connector, base)
>  
> @@ -73,6 +99,13 @@ struct drm_writeback_job {
>  	 * directly, use drm_atomic_set_writeback_fb_for_connector()
>  	 */
>  	struct drm_framebuffer *fb;
> +
> +	/**
> +	 * @out_fence:
> +	 *
> +	 * Fence which will signal once the writeback has completed
> +	 */
> +	struct dma_fence *out_fence;
>  };
>  
>  int drm_writeback_connector_init(struct drm_device *dev,
> @@ -85,5 +118,11 @@ void drm_writeback_queue_job(struct drm_writeback_connector *wb_connector,
>  			     struct drm_writeback_job *job);
>  
>  void drm_writeback_cleanup_job(struct drm_writeback_job *job);
> -void drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector);
> +
> +void
> +drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector,
> +				int status);
> +
> +struct dma_fence *
> +drm_writeback_get_out_fence(struct drm_writeback_connector *wb_connector);
>  #endif
> -- 
> 2.14.3
> 

-- 
Sean Paul, Software Engineer, Google / Chromium OS
_______________________________________________
Freedreno mailing list
Freedreno@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/freedreno

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [RFC 2/4] drm: writeback: Add out-fences for writeback connectors
@ 2018-02-23 16:14         ` Sean Paul
  0 siblings, 0 replies; 41+ messages in thread
From: Sean Paul @ 2018-02-23 16:14 UTC (permalink / raw)
  To: Rob Clark
  Cc: dri-devel, freedreno, linux-arm-msm, Brian Starkey, Liviu Dudau,
	Mihail Atanassov, Gustavo Padovan, Maarten Lankhorst, Sean Paul,
	David Airlie, linux-kernel

On Fri, Feb 23, 2018 at 08:17:52AM -0500, Rob Clark wrote:
> From: Brian Starkey <brian.starkey@arm.com>
> 
> Add the WRITEBACK_OUT_FENCE_PTR property to writeback connectors, to
> enable userspace to get a fence which will signal once the writeback is
> complete. It is not allowed to request an out-fence without a
> framebuffer attached to the connector.
> 
> A timeline is added to drm_writeback_connector for use by the writeback
> out-fences.
> 
> In the case of a commit failure or DRM_MODE_ATOMIC_TEST_ONLY, the fence
> is set to -1.
> 
> Changes from v2:
>  - Rebase onto Gustavo Padovan's v9 explicit sync series
>  - Change out_fence_ptr type to s32 __user *
>  - Set *out_fence_ptr to -1 in drm_atomic_connector_set_property
>  - Store fence in drm_writeback_job
>  Gustavo Padovan:
>  - Move out_fence_ptr out of connector_state
>  - Signal fence from drm_writeback_signal_completion instead of
>    in driver directly
> 
> Changes from v3:
>  - Rebase onto 7e9081c5aac7 drm/fence: fix memory overwrite when setting out_fence fd
>    (change out_fence_ptr to s32 __user *, for real this time.)
>  - Update documentation around WRITEBACK_OUT_FENCE_PTR
> 
> Signed-off-by: Brian Starkey <brian.starkey@arm.com>
> [rebased and fixed conflicts]
> Signed-off-by: Mihail Atanassov <mihail.atanassov@arm.com>
> Signed-off-by: Liviu Dudau <liviu.dudau@arm.com>
> Signed-off-by: Rob Clark <robdclark@gmail.com>

Reviewed-by: Sean Paul <seanpaul@chromium.org>

> ---
>  drivers/gpu/drm/drm_atomic.c    |  99 ++++++++++++++++++++++++++++++++----
>  drivers/gpu/drm/drm_writeback.c | 109 +++++++++++++++++++++++++++++++++++++++-
>  include/drm/drm_atomic.h        |   8 +++
>  include/drm/drm_connector.h     |   8 +--
>  include/drm/drm_mode_config.h   |   8 +++
>  include/drm/drm_writeback.h     |  41 ++++++++++++++-
>  6 files changed, 257 insertions(+), 16 deletions(-)
> 
> diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
> index 019f131fe8be..fc8c4da409ff 100644
> --- a/drivers/gpu/drm/drm_atomic.c
> +++ b/drivers/gpu/drm/drm_atomic.c
> @@ -318,6 +318,35 @@ static s32 __user *get_out_fence_for_crtc(struct drm_atomic_state *state,
>  	return fence_ptr;
>  }
>  
> +static int set_out_fence_for_connector(struct drm_atomic_state *state,
> +					struct drm_connector *connector,
> +					s32 __user *fence_ptr)
> +{
> +	unsigned int index = drm_connector_index(connector);
> +
> +	if (!fence_ptr)
> +		return 0;
> +
> +	if (put_user(-1, fence_ptr))
> +		return -EFAULT;
> +
> +	state->connectors[index].out_fence_ptr = fence_ptr;
> +
> +	return 0;
> +}
> +
> +static s32 __user *get_out_fence_for_connector(struct drm_atomic_state *state,
> +					       struct drm_connector *connector)
> +{
> +	unsigned int index = drm_connector_index(connector);
> +	s32 __user *fence_ptr;
> +
> +	fence_ptr = state->connectors[index].out_fence_ptr;
> +	state->connectors[index].out_fence_ptr = NULL;
> +
> +	return fence_ptr;
> +}
> +
>  /**
>   * drm_atomic_set_mode_for_crtc - set mode for CRTC
>   * @state: the CRTC whose incoming state to update
> @@ -676,6 +705,12 @@ static int drm_atomic_connector_check(struct drm_connector *connector,
>  		return -EINVAL;
>  	}
>  
> +	if (writeback_job->out_fence && !writeback_job->fb) {
> +		DRM_DEBUG_ATOMIC("[CONNECTOR:%d:%s] requesting out-fence without framebuffer\n",
> +				 connector->base.id, connector->name);
> +		return -EINVAL;
> +	}
> +
>  	return 0;
>  }
>  
> @@ -1277,6 +1312,11 @@ static int drm_atomic_connector_set_property(struct drm_connector *connector,
>  		if (fb)
>  			drm_framebuffer_unreference(fb);
>  		return ret;
> +	} else if (property == config->writeback_out_fence_ptr_property) {
> +		s32 __user *fence_ptr = u64_to_user_ptr(val);
> +
> +		return set_out_fence_for_connector(state->state, connector,
> +						   fence_ptr);
>  	} else if (connector->funcs->atomic_set_property) {
>  		return connector->funcs->atomic_set_property(connector,
>  				state, property, val);
> @@ -1361,6 +1401,8 @@ drm_atomic_connector_get_property(struct drm_connector *connector,
>  	} else if (property == config->writeback_fb_id_property) {
>  		/* Writeback framebuffer is one-shot, write and forget */
>  		*val = 0;
> +	} else if (property == config->writeback_out_fence_ptr_property) {
> +		*val = 0;
>  	} else if (connector->funcs->atomic_get_property) {
>  		return connector->funcs->atomic_get_property(connector,
>  				state, property, val);
> @@ -2221,7 +2263,7 @@ static int setup_out_fence(struct drm_out_fence_state *fence_state,
>  	return 0;
>  }
>  
> -static int prepare_crtc_signaling(struct drm_device *dev,
> +static int prepare_signaling(struct drm_device *dev,
>  				  struct drm_atomic_state *state,
>  				  struct drm_mode_atomic *arg,
>  				  struct drm_file *file_priv,
> @@ -2230,6 +2272,8 @@ static int prepare_crtc_signaling(struct drm_device *dev,
>  {
>  	struct drm_crtc *crtc;
>  	struct drm_crtc_state *crtc_state;
> +	struct drm_connector *conn;
> +	struct drm_connector_state *conn_state;
>  	int i, c = 0, ret;
>  
>  	if (arg->flags & DRM_MODE_ATOMIC_TEST_ONLY)
> @@ -2295,6 +2339,43 @@ static int prepare_crtc_signaling(struct drm_device *dev,
>  		c++;
>  	}
>  
> +	for_each_new_connector_in_state(state, conn, conn_state, i) {
> +		struct drm_writeback_job *job;
> +		struct drm_out_fence_state *f;
> +		struct dma_fence *fence;
> +		s32 __user *fence_ptr;
> +
> +		fence_ptr = get_out_fence_for_connector(state, conn);
> +		if (!fence_ptr)
> +			continue;
> +
> +		job = drm_atomic_get_writeback_job(conn_state);
> +		if (!job)
> +			return -ENOMEM;
> +
> +		f = krealloc(*fence_state, sizeof(**fence_state) *
> +			     (*num_fences + 1), GFP_KERNEL);
> +		if (!f)
> +			return -ENOMEM;
> +
> +		memset(&f[*num_fences], 0, sizeof(*f));
> +
> +		f[*num_fences].out_fence_ptr = fence_ptr;
> +		*fence_state = f;
> +
> +		fence = drm_writeback_get_out_fence((struct drm_writeback_connector *)conn);
> +		if (!fence)
> +			return -ENOMEM;
> +
> +		ret = setup_out_fence(&f[(*num_fences)++], fence);
> +		if (ret) {
> +			dma_fence_put(fence);
> +			return ret;
> +		}
> +
> +		job->out_fence = fence;
> +	}
> +
>  	/*
>  	 * Having this flag means user mode pends on event which will never
>  	 * reach due to lack of at least one CRTC for signaling
> @@ -2305,11 +2386,11 @@ static int prepare_crtc_signaling(struct drm_device *dev,
>  	return 0;
>  }
>  
> -static void complete_crtc_signaling(struct drm_device *dev,
> -				    struct drm_atomic_state *state,
> -				    struct drm_out_fence_state *fence_state,
> -				    unsigned int num_fences,
> -				    bool install_fds)
> +static void complete_signaling(struct drm_device *dev,
> +			       struct drm_atomic_state *state,
> +			       struct drm_out_fence_state *fence_state,
> +			       unsigned int num_fences,
> +			       bool install_fds)
>  {
>  	struct drm_crtc *crtc;
>  	struct drm_crtc_state *crtc_state;
> @@ -2488,8 +2569,8 @@ int drm_mode_atomic_ioctl(struct drm_device *dev,
>  		drm_mode_object_put(obj);
>  	}
>  
> -	ret = prepare_crtc_signaling(dev, state, arg, file_priv, &fence_state,
> -				     &num_fences);
> +	ret = prepare_signaling(dev, state, arg, file_priv, &fence_state,
> +				&num_fences);
>  	if (ret)
>  		goto out;
>  
> @@ -2507,7 +2588,7 @@ int drm_mode_atomic_ioctl(struct drm_device *dev,
>  out:
>  	drm_atomic_clean_old_fb(dev, plane_mask, ret);
>  
> -	complete_crtc_signaling(dev, state, fence_state, num_fences, !ret);
> +	complete_signaling(dev, state, fence_state, num_fences, !ret);
>  
>  	if (ret == -EDEADLK) {
>  		drm_atomic_state_clear(state);
> diff --git a/drivers/gpu/drm/drm_writeback.c b/drivers/gpu/drm/drm_writeback.c
> index da61f929cbc3..839e79932c19 100644
> --- a/drivers/gpu/drm/drm_writeback.c
> +++ b/drivers/gpu/drm/drm_writeback.c
> @@ -13,6 +13,7 @@
>  #include <drm/drm_property.h>
>  #include <drm/drm_writeback.h>
>  #include <drm/drmP.h>
> +#include <linux/dma-fence.h>
>  
>  /**
>   * DOC: overview
> @@ -30,6 +31,16 @@
>   * framebuffer applies only to a single commit (see below). A framebuffer may
>   * not be attached while the CRTC is off.
>   *
> + * Unlike with planes, when a writeback framebuffer is removed by userspace DRM
> + * makes no attempt to remove it from active use by the connector. This is
> + * because no method is provided to abort a writeback operation, and in any
> + * case making a new commit whilst a writeback is ongoing is undefined (see
> + * WRITEBACK_OUT_FENCE_PTR below). As soon as the current writeback is finished,
> + * the framebuffer will automatically no longer be in active use. As it will
> + * also have already been removed from the framebuffer list, there will be no
> + * way for any userspace application to retrieve a reference to it in the
> + * intervening period.
> + *
>   * Writeback connectors have some additional properties, which userspace
>   * can use to query and control them:
>   *
> @@ -46,8 +57,54 @@
>   *	data is an array of u32 DRM_FORMAT_* fourcc values.
>   *	Userspace can use this blob to find out what pixel formats are supported
>   *	by the connector's writeback engine.
> + *
> + *  "WRITEBACK_OUT_FENCE_PTR":
> + *	Userspace can use this property to provide a pointer for the kernel to
> + *	fill with a sync_file file descriptor, which will signal once the
> + *	writeback is finished. The value should be the address of a 32-bit
> + *	signed integer, cast to a u64.
> + *	Userspace should wait for this fence to signal before making another
> + *	commit affecting any of the same CRTCs, Planes or Connectors.
> + *	**Failure to do so will result in undefined behaviour.**
> + *	For this reason it is strongly recommended that all userspace
> + *	applications making use of writeback connectors *always* retrieve an
> + *	out-fence for the commit and use it appropriately.
> + *	From userspace, this property will always read as zero.
>   */
>  
> +#define fence_to_wb_connector(x) container_of(x->lock, \
> +					      struct drm_writeback_connector, \
> +					      fence_lock)
> +
> +static const char *drm_writeback_fence_get_driver_name(struct dma_fence *fence)
> +{
> +	struct drm_writeback_connector *wb_connector =
> +		fence_to_wb_connector(fence);
> +
> +	return wb_connector->base.dev->driver->name;
> +}
> +
> +static const char *
> +drm_writeback_fence_get_timeline_name(struct dma_fence *fence)
> +{
> +	struct drm_writeback_connector *wb_connector =
> +		fence_to_wb_connector(fence);
> +
> +	return wb_connector->timeline_name;
> +}
> +
> +static bool drm_writeback_fence_enable_signaling(struct dma_fence *fence)
> +{
> +	return true;
> +}
> +
> +static const struct dma_fence_ops drm_writeback_fence_ops = {
> +	.get_driver_name = drm_writeback_fence_get_driver_name,
> +	.get_timeline_name = drm_writeback_fence_get_timeline_name,
> +	.enable_signaling = drm_writeback_fence_enable_signaling,
> +	.wait = dma_fence_default_wait,
> +};
> +
>  static bool create_writeback_properties(struct drm_device *dev)
>  {
>  	struct drm_property *prop;
> @@ -69,6 +126,15 @@ static bool create_writeback_properties(struct drm_device *dev)
>  		dev->mode_config.writeback_pixel_formats_property = prop;
>  	}
>  
> +	if (!dev->mode_config.writeback_out_fence_ptr_property) {
> +		prop = drm_property_create_range(dev, DRM_MODE_PROP_ATOMIC,
> +						 "WRITEBACK_OUT_FENCE_PTR", 0,
> +						 U64_MAX);
> +		if (!prop)
> +			return -ENOMEM;
> +		dev->mode_config.writeback_out_fence_ptr_property = prop;
> +	}
> +
>  	return true;
>  }
>  
> @@ -138,6 +204,15 @@ int drm_writeback_connector_init(struct drm_device *dev,
>  	INIT_LIST_HEAD(&wb_connector->job_queue);
>  	spin_lock_init(&wb_connector->job_lock);
>  
> +	wb_connector->fence_context = dma_fence_context_alloc(1);
> +	spin_lock_init(&wb_connector->fence_lock);
> +	snprintf(wb_connector->timeline_name,
> +		 sizeof(wb_connector->timeline_name),
> +		 "CONNECTOR:%d-%s", connector->base.id, connector->name);
> +
> +	drm_object_attach_property(&connector->base,
> +				   config->writeback_out_fence_ptr_property, 0);
> +
>  	drm_object_attach_property(&connector->base,
>  				   config->writeback_fb_id_property, 0);
>  
> @@ -200,6 +275,7 @@ void drm_writeback_cleanup_job(struct drm_writeback_job *job)
>  
>  	if (job->fb)
>  		drm_framebuffer_unreference(job->fb);
> +	dma_fence_put(job->out_fence);
>  	kfree(job);
>  }
>  EXPORT_SYMBOL(drm_writeback_cleanup_job);
> @@ -222,6 +298,7 @@ static void cleanup_work(struct work_struct *work)
>  /**
>   * drm_writeback_signal_completion - Signal the completion of a writeback job
>   * @wb_connector: The writeback connector whose job is complete
> + * @status: Status code to set in the writeback out_fence (0 for success)
>   *
>   * Drivers should call this to signal the completion of a previously queued
>   * writeback job. It should be called as soon as possible after the hardware
> @@ -235,7 +312,8 @@ static void cleanup_work(struct work_struct *work)
>   * See also: drm_writeback_queue_job()
>   */
>  void
> -drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector)
> +drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector,
> +				int status)
>  {
>  	unsigned long flags;
>  	struct drm_writeback_job *job;
> @@ -244,8 +322,14 @@ drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector)
>  	job = list_first_entry_or_null(&wb_connector->job_queue,
>  				       struct drm_writeback_job,
>  				       list_entry);
> -	if (job)
> +	if (job) {
>  		list_del(&job->list_entry);
> +		if (job->out_fence) {
> +			if (status)
> +				dma_fence_set_error(job->out_fence, status);
> +			dma_fence_signal(job->out_fence);
> +		}
> +	}
>  	spin_unlock_irqrestore(&wb_connector->job_lock, flags);
>  
>  	if (WARN_ON(!job))
> @@ -255,3 +339,24 @@ drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector)
>  	queue_work(system_long_wq, &job->cleanup_work);
>  }
>  EXPORT_SYMBOL(drm_writeback_signal_completion);
> +
> +struct dma_fence *
> +drm_writeback_get_out_fence(struct drm_writeback_connector *wb_connector)
> +{
> +	struct dma_fence *fence;
> +
> +	if (WARN_ON(wb_connector->base.connector_type !=
> +		    DRM_MODE_CONNECTOR_WRITEBACK))
> +		return NULL;
> +
> +	fence = kzalloc(sizeof(*fence), GFP_KERNEL);
> +	if (!fence)
> +		return NULL;
> +
> +	dma_fence_init(fence, &drm_writeback_fence_ops,
> +		       &wb_connector->fence_lock, wb_connector->fence_context,
> +		       ++wb_connector->fence_seqno);
> +
> +	return fence;
> +}
> +EXPORT_SYMBOL(drm_writeback_get_out_fence);
> diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
> index d7b0263cc5cf..09076a625637 100644
> --- a/include/drm/drm_atomic.h
> +++ b/include/drm/drm_atomic.h
> @@ -160,6 +160,14 @@ struct __drm_crtcs_state {
>  struct __drm_connnectors_state {
>  	struct drm_connector *ptr;
>  	struct drm_connector_state *state, *old_state, *new_state;
> +	/**
> +	 * @out_fence_ptr:
> +	 *
> +	 * User-provided pointer which the kernel uses to return a sync_file
> +	 * file descriptor. Used by writeback connectors to signal completion of
> +	 * the writeback.
> +	 */
> +	s32 __user *out_fence_ptr;
>  };
>  
>  struct drm_private_obj;
> diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
> index 8701ebcc68b3..da56fda6e218 100644
> --- a/include/drm/drm_connector.h
> +++ b/include/drm/drm_connector.h
> @@ -429,10 +429,10 @@ struct drm_connector_state {
>  	/**
>  	 * @writeback_job: Writeback job for writeback connectors
>  	 *
> -	 * Holds the framebuffer for a writeback connector. As the writeback
> -	 * completion may be asynchronous to the normal commit cycle, the
> -	 * writeback job lifetime is managed separately from the normal atomic
> -	 * state by this object.
> +	 * Holds the framebuffer and out-fence for a writeback connector. As
> +	 * the writeback completion may be asynchronous to the normal commit
> +	 * cycle, the writeback job lifetime is managed separately from the
> +	 * normal atomic state by this object.
>  	 *
>  	 * See also: drm_writeback_queue_job() and
>  	 * drm_writeback_signal_completion()
> diff --git a/include/drm/drm_mode_config.h b/include/drm/drm_mode_config.h
> index c012e1148ec0..930e00560380 100644
> --- a/include/drm/drm_mode_config.h
> +++ b/include/drm/drm_mode_config.h
> @@ -792,6 +792,14 @@ struct drm_mode_config {
>  	 * See also: drm_writeback_connector_init()
>  	 */
>  	struct drm_property *writeback_pixel_formats_property;
> +	/**
> +	 * @writeback_out_fence_ptr_property: Property for writeback connectors,
> +	 * fd pointer representing the outgoing fences for a writeback
> +	 * connector. Userspace should provide a pointer to a value of type s32,
> +	 * and then cast that pointer to u64.
> +	 * See also: drm_writeback_connector_init()
> +	 */
> +	struct drm_property *writeback_out_fence_ptr_property;
>  
>  	/* dumb ioctl parameters */
>  	uint32_t preferred_depth, prefer_shadow;
> diff --git a/include/drm/drm_writeback.h b/include/drm/drm_writeback.h
> index 0bb95fd4907d..b9470ae2a53d 100644
> --- a/include/drm/drm_writeback.h
> +++ b/include/drm/drm_writeback.h
> @@ -49,6 +49,32 @@ struct drm_writeback_connector {
>  	 * drm_writeback_signal_completion()
>  	 */
>  	struct list_head job_queue;
> +
> +	/**
> +	 * @fence_context:
> +	 *
> +	 * timeline context used for fence operations.
> +	 */
> +	unsigned int fence_context;
> +	/**
> +	 * @fence_lock:
> +	 *
> +	 * spinlock to protect the fences in the fence_context.
> +	 */
> +	spinlock_t fence_lock;
> +	/**
> +	 * @fence_seqno:
> +	 *
> +	 * Seqno variable used as monotonic counter for the fences
> +	 * created on the connector's timeline.
> +	 */
> +	unsigned long fence_seqno;
> +	/**
> +	 * @timeline_name:
> +	 *
> +	 * The name of the connector's fence timeline.
> +	 */
> +	char timeline_name[32];
>  };
>  #define to_wb_connector(x) container_of(x, struct drm_writeback_connector, base)
>  
> @@ -73,6 +99,13 @@ struct drm_writeback_job {
>  	 * directly, use drm_atomic_set_writeback_fb_for_connector()
>  	 */
>  	struct drm_framebuffer *fb;
> +
> +	/**
> +	 * @out_fence:
> +	 *
> +	 * Fence which will signal once the writeback has completed
> +	 */
> +	struct dma_fence *out_fence;
>  };
>  
>  int drm_writeback_connector_init(struct drm_device *dev,
> @@ -85,5 +118,11 @@ void drm_writeback_queue_job(struct drm_writeback_connector *wb_connector,
>  			     struct drm_writeback_job *job);
>  
>  void drm_writeback_cleanup_job(struct drm_writeback_job *job);
> -void drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector);
> +
> +void
> +drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector,
> +				int status);
> +
> +struct dma_fence *
> +drm_writeback_get_out_fence(struct drm_writeback_connector *wb_connector);
>  #endif
> -- 
> 2.14.3
> 

-- 
Sean Paul, Software Engineer, Google / Chromium OS

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [RFC 1/4] drm: Add writeback connector type
  2018-02-23 15:59         ` Sean Paul
@ 2018-02-23 16:21           ` Liviu Dudau
  -1 siblings, 0 replies; 41+ messages in thread
From: Liviu Dudau @ 2018-02-23 16:21 UTC (permalink / raw)
  To: Sean Paul
  Cc: Jonathan Corbet, David Airlie, linux-arm-msm, linux-doc,
	linux-kernel, dri-devel, Mihail Atanassov, freedreno

On Fri, Feb 23, 2018 at 10:59:35AM -0500, Sean Paul wrote:
> On Fri, Feb 23, 2018 at 08:17:51AM -0500, Rob Clark wrote:
> > From: Brian Starkey <brian.starkey@arm.com>
> > 
> > Writeback connectors represent writeback engines which can write the
> > CRTC output to a memory framebuffer. Add a writeback connector type and
> > related support functions.
> > 
> > Drivers should initialize a writeback connector with
> > drm_writeback_connector_init() which takes care of setting up all the
> > writeback-specific details on top of the normal functionality of
> > drm_connector_init().
> > 
> > Writeback connectors have a WRITEBACK_FB_ID property, used to set the
> > output framebuffer, and a WRITEBACK_PIXEL_FORMATS blob used to expose the
> > supported writeback formats to userspace.
> > 
> > When a framebuffer is attached to a writeback connector with the
> > WRITEBACK_FB_ID property, it is used only once (for the commit in which
> > it was included), and userspace can never read back the value of
> > WRITEBACK_FB_ID. WRITEBACK_FB_ID can only be set if the connector is
> > attached to a CRTC.
> > 
> > Changes since v1:
> >  - Added drm_writeback.c + documentation
> >  - Added helper to initialize writeback connector in one go
> >  - Added core checks
> >  - Squashed into a single commit
> >  - Dropped the client cap
> >  - Writeback framebuffers are no longer persistent
> > 
> > Changes since v2:
> >  Daniel Vetter:
> >  - Subclass drm_connector to drm_writeback_connector
> >  - Relax check to allow CRTC to be set without an FB
> >  - Add some writeback_ prefixes
> >  - Drop PIXEL_FORMATS_SIZE property, as it was unnecessary
> >  Gustavo Padovan:
> >  - Add drm_writeback_job to handle writeback signalling centrally
> > 
> > Changes since v3:
> >  - Rebased
> >  - Rename PIXEL_FORMATS -> WRITEBACK_PIXEL_FORMATS
> > 
> > Changes since v4:
> >  - Added atomic_commit() vfunc to connector helper funcs, so that
> >    writeback jobs are committed from atomic helpers
> > 
> > Signed-off-by: Brian Starkey <brian.starkey@arm.com>
> > [rebased and fixed conflicts]
> > Signed-off-by: Mihail Atanassov <mihail.atanassov@arm.com>
> > Signed-off-by: Liviu Dudau <liviu.dudau@arm.com>
> > [rebased and added atomic_commit() vfunc for writeback jobs]
> > Signed-off-by: Rob Clark <robdclark@gmail.com>
> > ---
> >  Documentation/gpu/drm-kms.rst            |   9 ++
> >  drivers/gpu/drm/Makefile                 |   2 +-
> >  drivers/gpu/drm/drm_atomic.c             | 130 ++++++++++++++++
> >  drivers/gpu/drm/drm_atomic_helper.c      |  30 ++++
> >  drivers/gpu/drm/drm_connector.c          |   4 +-
> >  drivers/gpu/drm/drm_writeback.c          | 257 +++++++++++++++++++++++++++++++
> >  include/drm/drm_atomic.h                 |   3 +
> >  include/drm/drm_connector.h              |  13 ++
> >  include/drm/drm_mode_config.h            |  14 ++
> >  include/drm/drm_modeset_helper_vtables.h |  11 ++
> >  include/drm/drm_writeback.h              |  89 +++++++++++
> >  include/uapi/drm/drm_mode.h              |   1 +
> >  12 files changed, 561 insertions(+), 2 deletions(-)
> >  create mode 100644 drivers/gpu/drm/drm_writeback.c
> >  create mode 100644 include/drm/drm_writeback.h
> > 
> > diff --git a/Documentation/gpu/drm-kms.rst b/Documentation/gpu/drm-kms.rst
> > index 2dcf5b42015d..e7590dd2f71e 100644
> > --- a/Documentation/gpu/drm-kms.rst
> > +++ b/Documentation/gpu/drm-kms.rst
> > @@ -370,6 +370,15 @@ Connector Functions Reference
> >  .. kernel-doc:: drivers/gpu/drm/drm_connector.c
> >     :export:
> >  
> > +Writeback Connectors
> > +--------------------
> > +
> > +.. kernel-doc:: drivers/gpu/drm/drm_writeback.c
> > +  :doc: overview
> > +
> > +.. kernel-doc:: drivers/gpu/drm/drm_writeback.c
> > +  :export:
> > +
> >  Encoder Abstraction
> >  ===================
> >  
> > diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> > index 50093ff4479b..3d708959b224 100644
> > --- a/drivers/gpu/drm/Makefile
> > +++ b/drivers/gpu/drm/Makefile
> > @@ -18,7 +18,7 @@ drm-y       :=	drm_auth.o drm_bufs.o drm_cache.o \
> >  		drm_encoder.o drm_mode_object.o drm_property.o \
> >  		drm_plane.o drm_color_mgmt.o drm_print.o \
> >  		drm_dumb_buffers.o drm_mode_config.o drm_vblank.o \
> > -		drm_syncobj.o drm_lease.o
> > +		drm_syncobj.o drm_lease.o drm_writeback.o
> >  
> >  drm-$(CONFIG_DRM_LIB_RANDOM) += lib/drm_random.o
> >  drm-$(CONFIG_DRM_VM) += drm_vm.o
> > diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
> > index 46733d534587..019f131fe8be 100644
> > --- a/drivers/gpu/drm/drm_atomic.c
> > +++ b/drivers/gpu/drm/drm_atomic.c
> > @@ -30,6 +30,7 @@
> >  #include <drm/drm_atomic.h>
> >  #include <drm/drm_mode.h>
> >  #include <drm/drm_print.h>
> > +#include <drm/drm_writeback.h>
> >  #include <linux/sync_file.h>
> >  
> >  #include "drm_crtc_internal.h"
> > @@ -638,6 +639,46 @@ static void drm_atomic_crtc_print_state(struct drm_printer *p,
> >  		crtc->funcs->atomic_print_state(p, state);
> >  }
> >  
> > +/**
> > + * drm_atomic_connector_check - check connector state
> > + * @connector: connector to check
> > + * @state: connector state to check
> > + *
> > + * Provides core sanity checks for connector state.
> > + *
> > + * RETURNS:
> > + * Zero on success, error code on failure
> > + */
> > +static int drm_atomic_connector_check(struct drm_connector *connector,
> > +		struct drm_connector_state *state)
> > +{
> > +	struct drm_crtc_state *crtc_state;
> > +	struct drm_writeback_job *writeback_job = state->writeback_job;
> > +
> > +	if ((connector->connector_type != DRM_MODE_CONNECTOR_WRITEBACK) ||
> > +	    !writeback_job)
> > +		return 0;
> > +
> > +	if (writeback_job->fb && !state->crtc) {
> > +		DRM_DEBUG_ATOMIC("[CONNECTOR:%d:%s] framebuffer without CRTC\n",
> > +				 connector->base.id, connector->name);
> > +		return -EINVAL;
> > +	}
> > +
> > +	if (state->crtc)
> > +		crtc_state = drm_atomic_get_existing_crtc_state(state->state,
> > +								state->crtc);
> > +
> > +	if (writeback_job->fb && !crtc_state->active) {
> 
> Checking for writeback_job->fb is redundant here and above since it's checked
> first thing as early exit criteria.

I somehow don't see that check. We check if (!writebac_job) and then all
other checks are logic ands with other conditions, so I think the check
here is still relevant.

> 
> > +		DRM_DEBUG_ATOMIC("[CONNECTOR:%d:%s] has framebuffer, but [CRTC:%d] is off\n",
> > +				 connector->base.id, connector->name,
> > +				 state->crtc->base.id);
> > +		return -EINVAL;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> >  /**
> >   * drm_atomic_get_plane_state - get plane state
> >   * @state: global atomic state object
> > @@ -1230,6 +1271,12 @@ static int drm_atomic_connector_set_property(struct drm_connector *connector,
> >  			return -EINVAL;
> >  		}
> >  		state->content_protection = val;
> > +	} else if (property == config->writeback_fb_id_property) {
> > +		struct drm_framebuffer *fb = drm_framebuffer_lookup(dev, NULL, val);
> > +		int ret = drm_atomic_set_writeback_fb_for_connector(state, fb);
> > +		if (fb)
> > +			drm_framebuffer_unreference(fb);
> > +		return ret;
> >  	} else if (connector->funcs->atomic_set_property) {
> >  		return connector->funcs->atomic_set_property(connector,
> >  				state, property, val);
> > @@ -1311,6 +1358,9 @@ drm_atomic_connector_get_property(struct drm_connector *connector,
> >  		*val = state->scaling_mode;
> >  	} else if (property == connector->content_protection_property) {
> >  		*val = state->content_protection;
> > +	} else if (property == config->writeback_fb_id_property) {
> > +		/* Writeback framebuffer is one-shot, write and forget */
> > +		*val = 0;
> >  	} else if (connector->funcs->atomic_get_property) {
> >  		return connector->funcs->atomic_get_property(connector,
> >  				state, property, val);
> > @@ -1518,6 +1568,75 @@ drm_atomic_set_crtc_for_connector(struct drm_connector_state *conn_state,
> >  }
> >  EXPORT_SYMBOL(drm_atomic_set_crtc_for_connector);
> >  
> > +/*
> > + * drm_atomic_get_writeback_job - return or allocate a writeback job
> > + * @conn_state: Connector state to get the job for
> > + *
> > + * Writeback jobs have a different lifetime to the atomic state they are
> > + * associated with. This convenience function takes care of allocating a job
> > + * if there isn't yet one associated with the connector state, otherwise
> > + * it just returns the existing job.
> > + *
> > + * Returns: The writeback job for the given connector state
> > + */
> > +static struct drm_writeback_job *
> > +drm_atomic_get_writeback_job(struct drm_connector_state *conn_state)
> > +{
> > +	WARN_ON(conn_state->connector->connector_type !=
> > +		DRM_MODE_CONNECTOR_WRITEBACK);
> > +
> > +	if (!conn_state->writeback_job)
> > +		conn_state->writeback_job =
> > +			kzalloc(sizeof(*conn_state->writeback_job), GFP_KERNEL);
> > +
> > +	return conn_state->writeback_job;
> > +}
> > +
> > +/**
> > + * drm_atomic_set_writeback_fb_for_connector - set writeback framebuffer
> > + * @conn_state: atomic state object for the connector
> > + * @fb: fb to use for the connector
> > + *
> > + * This is used to set the framebuffer for a writeback connector, which outputs
> > + * to a buffer instead of an actual physical connector.
> > + * Changing the assigned framebuffer requires us to grab a reference to the new
> > + * fb and drop the reference to the old fb, if there is one. This function
> > + * takes care of all these details besides updating the pointer in the
> > + * state object itself.
> > + *
> > + * Note: The only way conn_state can already have an fb set is if the commit
> > + * sets the property more than once.
> > + *
> > + * See also: drm_writeback_connector_init()
> > + *
> > + * Returns: 0 on success
> > + */
> > +int drm_atomic_set_writeback_fb_for_connector(
> > +		struct drm_connector_state *conn_state,
> > +		struct drm_framebuffer *fb)
> > +{
> > +	struct drm_writeback_job *job =
> > +		drm_atomic_get_writeback_job(conn_state);
> > +	if (!job)
> > +		return -ENOMEM;
> > +
> > +	if (job->fb)
> > +		drm_framebuffer_unreference(job->fb);
> > +	if (fb)
> > +		drm_framebuffer_reference(fb);
> > +	job->fb = fb;
> > +
> > +	if (fb)
> > +		DRM_DEBUG_ATOMIC("Set [FB:%d] for connector state %p\n",
> > +				 fb->base.id, conn_state);
> > +	else
> > +		DRM_DEBUG_ATOMIC("Set [NOFB] for connector state %p\n",
> > +				 conn_state);
> > +
> > +	return 0;
> > +}
> > +EXPORT_SYMBOL(drm_atomic_set_writeback_fb_for_connector);
> > +
> >  /**
> >   * drm_atomic_add_affected_connectors - add connectors for crtc
> >   * @state: atomic state
> > @@ -1636,6 +1755,8 @@ int drm_atomic_check_only(struct drm_atomic_state *state)
> >  	struct drm_plane_state *plane_state;
> >  	struct drm_crtc *crtc;
> >  	struct drm_crtc_state *crtc_state;
> > +	struct drm_connector *conn;
> > +	struct drm_connector_state *conn_state;
> >  	int i, ret = 0;
> >  
> >  	DRM_DEBUG_ATOMIC("checking %p\n", state);
> > @@ -1658,6 +1779,15 @@ int drm_atomic_check_only(struct drm_atomic_state *state)
> >  		}
> >  	}
> >  
> > +	for_each_new_connector_in_state(state, conn, conn_state, i) {
> > +		ret = drm_atomic_connector_check(conn, conn_state);
> > +		if (ret) {
> > +			DRM_DEBUG_ATOMIC("[CONNECTOR:%d:%s] atomic core check failed\n",
> > +					 conn->base.id, conn->name);
> > +			return ret;
> > +		}
> > +	}
> > +
> >  	if (config->funcs->atomic_check)
> >  		ret = config->funcs->atomic_check(state->dev, state);
> >  
> > diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c
> > index ae3cbfe9e01c..12b910755d84 100644
> > --- a/drivers/gpu/drm/drm_atomic_helper.c
> > +++ b/drivers/gpu/drm/drm_atomic_helper.c
> > @@ -30,6 +30,7 @@
> >  #include <drm/drm_plane_helper.h>
> >  #include <drm/drm_crtc_helper.h>
> >  #include <drm/drm_atomic_helper.h>
> > +#include <drm/drm_writeback.h>
> >  #include <linux/dma-fence.h>
> >  
> >  #include "drm_crtc_helper_internal.h"
> > @@ -1159,6 +1160,27 @@ void drm_atomic_helper_commit_modeset_disables(struct drm_device *dev,
> >  }
> >  EXPORT_SYMBOL(drm_atomic_helper_commit_modeset_disables);
> >  
> > +static void commit_writebacks(struct drm_device *dev, struct drm_atomic_state *old_state)
> > +{
> > +	struct drm_connector *connector;
> > +	struct drm_connector_state *new_conn_state;
> > +	int i;
> > +
> > +	for_each_new_connector_in_state(old_state, connector, new_conn_state, i) {
> > +		const struct drm_connector_helper_funcs *funcs;
> > +
> > +		funcs = connector->helper_private;
> > +
> > +		if (new_conn_state->writeback_job &&
> > +		    new_conn_state->writeback_job->fb) {
> > +			WARN_ON(connector->connector_type !=
> > +				DRM_MODE_CONNECTOR_WRITEBACK);
> > +			funcs->atomic_commit(connector,
> > +					     new_conn_state->writeback_job);
> > +		}
> > +	}
> > +}
> > +
> >  /**
> >   * drm_atomic_helper_commit_modeset_enables - modeset commit to enable outputs
> >   * @dev: DRM device
> > @@ -1238,6 +1260,8 @@ void drm_atomic_helper_commit_modeset_enables(struct drm_device *dev,
> >  
> >  		drm_bridge_enable(encoder->bridge);
> >  	}
> > +
> > +	commit_writebacks(dev, old_state);
> >  }
> >  EXPORT_SYMBOL(drm_atomic_helper_commit_modeset_enables);
> >  
> > @@ -3627,6 +3651,9 @@ __drm_atomic_helper_connector_duplicate_state(struct drm_connector *connector,
> >  	if (state->crtc)
> >  		drm_connector_get(connector);
> >  	state->commit = NULL;
> > +
> > +	/* Don't copy over a writeback job, they are used only once */
> > +	state->writeback_job = NULL;
> >  }
> >  EXPORT_SYMBOL(__drm_atomic_helper_connector_duplicate_state);
> >  
> > @@ -3756,6 +3783,9 @@ __drm_atomic_helper_connector_destroy_state(struct drm_connector_state *state)
> >  
> >  	if (state->commit)
> >  		drm_crtc_commit_put(state->commit);
> > +
> > +	if (state->writeback_job)
> > +		drm_writeback_cleanup_job(state->writeback_job);
> >  }
> >  EXPORT_SYMBOL(__drm_atomic_helper_connector_destroy_state);
> >  
> > diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c
> > index 16b9c3810af2..add47f06ae70 100644
> > --- a/drivers/gpu/drm/drm_connector.c
> > +++ b/drivers/gpu/drm/drm_connector.c
> > @@ -87,6 +87,7 @@ static struct drm_conn_prop_enum_list drm_connector_enum_list[] = {
> >  	{ DRM_MODE_CONNECTOR_VIRTUAL, "Virtual" },
> >  	{ DRM_MODE_CONNECTOR_DSI, "DSI" },
> >  	{ DRM_MODE_CONNECTOR_DPI, "DPI" },
> > +	{ DRM_MODE_CONNECTOR_WRITEBACK, "Writeback" },
> >  };
> >  
> >  void drm_connector_ida_init(void)
> > @@ -249,7 +250,8 @@ int drm_connector_init(struct drm_device *dev,
> >  	config->num_connector++;
> >  	spin_unlock_irq(&config->connector_list_lock);
> >  
> > -	if (connector_type != DRM_MODE_CONNECTOR_VIRTUAL)
> > +	if (connector_type != DRM_MODE_CONNECTOR_VIRTUAL &&
> > +	    connector_type != DRM_MODE_CONNECTOR_WRITEBACK)
> >  		drm_object_attach_property(&connector->base,
> >  					      config->edid_property,
> >  					      0);
> > diff --git a/drivers/gpu/drm/drm_writeback.c b/drivers/gpu/drm/drm_writeback.c
> > new file mode 100644
> > index 000000000000..da61f929cbc3
> > --- /dev/null
> > +++ b/drivers/gpu/drm/drm_writeback.c
> > @@ -0,0 +1,257 @@
> > +/*
> > + * (C) COPYRIGHT 2016 ARM Limited. All rights reserved.
> > + * Author: Brian Starkey <brian.starkey@arm.com>
> > + *
> > + * This program is free software and is provided to you under the terms of the
> > + * GNU General Public License version 2 as published by the Free Software
> > + * Foundation, and any use by you of this program is subject to the terms
> > + * of such GNU licence.
> > + */
> > +
> > +#include <drm/drm_crtc.h>
> > +#include <drm/drm_modeset_helper_vtables.h>
> > +#include <drm/drm_property.h>
> > +#include <drm/drm_writeback.h>
> > +#include <drm/drmP.h>
> > +
> > +/**
> > + * DOC: overview
> > + *
> > + * Writeback connectors are used to expose hardware which can write the output
> > + * from a CRTC to a memory buffer. They are used and act similarly to other
> > + * types of connectors, with some important differences:
> > + *  - Writeback connectors don't provide a way to output visually to the user.
> > + *  - Writeback connectors should always report as "disconnected" (so that
> > + *    clients which don't understand them will ignore them).
> 
> Have we considered hiding writeback behind a client cap instead?

Yes, changelog says that the client cap was dropped in v2 based on
feedback (as far as I remember). 

> 
> > + *  - Writeback connectors don't have EDID.
> > + *
> > + * A framebuffer may only be attached to a writeback connector when the
> > + * connector is attached to a CRTC. The WRITEBACK_FB_ID property which sets the
> > + * framebuffer applies only to a single commit (see below). A framebuffer may
> > + * not be attached while the CRTC is off.
> > + *
> > + * Writeback connectors have some additional properties, which userspace
> > + * can use to query and control them:
> > + *
> > + *  "WRITEBACK_FB_ID":
> > + *	Write-only object property storing a DRM_MODE_OBJECT_FB: it stores the
> > + *	framebuffer to be written by the writeback connector. This property is
> > + *	similar to the FB_ID property on planes, but will always read as zero
> > + *	and is not preserved across commits.
> > + *	Userspace must set this property to an output buffer every time it
> > + *	wishes the buffer to get filled.
> > + *
> > + *  "WRITEBACK_PIXEL_FORMATS":
> > + *	Immutable blob property to store the supported pixel formats table. The
> > + *	data is an array of u32 DRM_FORMAT_* fourcc values.
> > + *	Userspace can use this blob to find out what pixel formats are supported
> > + *	by the connector's writeback engine.
> > + */
> > +
> > +static bool create_writeback_properties(struct drm_device *dev)
> > +{
> > +	struct drm_property *prop;
> > +
> > +	if (!dev->mode_config.writeback_fb_id_property) {
> 
> Somewhat an aside, but we should probably come up with more formal rules on
> where to stick properties. We have some in drm_connector, which I interpret as
> "connector-only properties", but we also have a bunch in mode_config.
> 
> For optional properties which will generally only apply to one connector on a
> system, I'd be inclined to put it in connector. 

I can move the writeback-related properties into the connector in the
next series.


> 
> > +		prop = drm_property_create_object(dev, DRM_MODE_PROP_ATOMIC,
> > +						  "WRITEBACK_FB_ID",
> > +						  DRM_MODE_OBJECT_FB);
> > +		if (!prop)
> > +			return false;
> > +		dev->mode_config.writeback_fb_id_property = prop;
> > +	}
> > +
> > +	if (!dev->mode_config.writeback_pixel_formats_property) {
> > +		prop = drm_property_create(dev, DRM_MODE_PROP_BLOB | DRM_MODE_PROP_IMMUTABLE,
> > +					   "WRITEBACK_PIXEL_FORMATS", 0);
> 
> Does it make sense to expose the formats if u/s doesn't support atomic and can't
> use writeback? Perhaps this should also be DRM_MODE_PROP_ATOMIC.

Agree, probably all the writeback properties should only be available if
the client selects DRM_MODE_PROP_ATOMIC.

> 
> 
> > +		if (!prop)
> > +			return false;
> > +		dev->mode_config.writeback_pixel_formats_property = prop;
> > +	}
> > +
> > +	return true;
> > +}
> > +
> > +static const struct drm_encoder_funcs drm_writeback_encoder_funcs = {
> > +	.destroy = drm_encoder_cleanup,
> > +};
> > +
> > +/**
> > + * drm_writeback_connector_init - Initialize a writeback connector and its properties
> > + * @dev: DRM device
> > + * @wb_connector: Writeback connector to initialize
> > + * @con_funcs: Connector funcs vtable
> > + * @enc_helper_funcs: Encoder helper funcs vtable to be used by the internal encoder
> > + * @formats: Array of supported pixel formats for the writeback engine
> > + * @n_formats: Length of the formats array
> > + *
> > + * This function creates the writeback-connector-specific properties if they
> > + * have not been already created, initializes the connector as
> > + * type DRM_MODE_CONNECTOR_WRITEBACK, and correctly initializes the property
> > + * values. It will also create an internal encoder associated with the
> > + * drm_writeback_connector and set it to use the @enc_helper_funcs vtable for
> > + * the encoder helper.
> > + *
> > + * Drivers should always use this function instead of drm_connector_init() to
> > + * set up writeback connectors.
> > + *
> > + * Returns: 0 on success, or a negative error code
> > + */
> > +int drm_writeback_connector_init(struct drm_device *dev,
> > +				 struct drm_writeback_connector *wb_connector,
> > +				 const struct drm_connector_funcs *con_funcs,
> > +				 const struct drm_encoder_helper_funcs *enc_helper_funcs,
> > +				 const u32 *formats, int n_formats)
> > +{
> > +	int ret;
> > +	struct drm_property_blob *blob;
> > +	struct drm_connector *connector = &wb_connector->base;
> > +	struct drm_mode_config *config = &dev->mode_config;
> > +
> > +	if (!create_writeback_properties(dev))
> > +		return -EINVAL;
> > +
> > +	blob = drm_property_create_blob(dev, n_formats * sizeof(*formats),
> > +					formats);
> > +	if (IS_ERR(blob))
> > +		return PTR_ERR(blob);
> > +
> > +	drm_encoder_helper_add(&wb_connector->encoder, enc_helper_funcs);
> > +	ret = drm_encoder_init(dev, &wb_connector->encoder,
> > +			       &drm_writeback_encoder_funcs,
> > +			       DRM_MODE_ENCODER_VIRTUAL, NULL);
> > +	if (ret)
> > +		goto fail;
> > +
> > +	connector->interlace_allowed = 0;
> > +
> > +	ret = drm_connector_init(dev, connector, con_funcs,
> > +				 DRM_MODE_CONNECTOR_WRITEBACK);
> > +	if (ret)
> > +		goto connector_fail;
> > +
> > +	ret = drm_mode_connector_attach_encoder(connector,
> > +						&wb_connector->encoder);
> > +	if (ret)
> > +		goto attach_fail;
> > +
> > +	INIT_LIST_HEAD(&wb_connector->job_queue);
> > +	spin_lock_init(&wb_connector->job_lock);
> > +
> > +	drm_object_attach_property(&connector->base,
> > +				   config->writeback_fb_id_property, 0);
> > +
> > +	drm_object_attach_property(&connector->base,
> > +				   config->writeback_pixel_formats_property,
> > +				   blob->base.id);
> > +	wb_connector->pixel_formats_blob_ptr = blob;
> > +
> > +	return 0;
> > +
> > +attach_fail:
> > +	drm_connector_cleanup(connector);
> > +connector_fail:
> > +	drm_encoder_cleanup(&wb_connector->encoder);
> > +fail:
> > +	drm_property_unreference_blob(blob);
> > +	return ret;
> > +}
> > +EXPORT_SYMBOL(drm_writeback_connector_init);
> > +
> > +/**
> > + * drm_writeback_queue_job - Queue a writeback job for later signalling
> > + * @wb_connector: The writeback connector to queue a job on
> > + * @job: The job to queue
> > + *
> > + * This function adds a job to the job_queue for a writeback connector. It
> > + * should be considered to take ownership of the writeback job, and so any other
> > + * references to the job must be cleared after calling this function.
> > + *
> > + * Drivers must ensure that for a given writeback connector, jobs are queued in
> > + * exactly the same order as they will be completed by the hardware (and
> > + * signaled via drm_writeback_signal_completion).
> > + *
> > + * For every call to drm_writeback_queue_job() there must be exactly one call to
> > + * drm_writeback_signal_completion()
> > + *
> > + * See also: drm_writeback_signal_completion()
> > + */
> > +void drm_writeback_queue_job(struct drm_writeback_connector *wb_connector,
> > +			     struct drm_writeback_job *job)
> > +{
> > +	unsigned long flags;
> > +
> > +	spin_lock_irqsave(&wb_connector->job_lock, flags);
> > +	list_add_tail(&job->list_entry, &wb_connector->job_queue);
> > +	spin_unlock_irqrestore(&wb_connector->job_lock, flags);
> > +}
> > +EXPORT_SYMBOL(drm_writeback_queue_job);
> > +
> > +/**
> > + * drm_writeback_cleanup_job - Cleanup and free a writeback job
> > + * @job: The writeback job to free
> > + *
> > + * Drops any references held by the writeback job, and frees the structure.
> > + */
> > +void drm_writeback_cleanup_job(struct drm_writeback_job *job)
> > +{
> > +	if (!job)
> > +		return;
> > +
> > +	if (job->fb)
> > +		drm_framebuffer_unreference(job->fb);
> > +	kfree(job);
> > +}
> > +EXPORT_SYMBOL(drm_writeback_cleanup_job);
> > +
> > +/*
> > + * @cleanup_work: deferred cleanup of a writeback job
> > + *
> > + * The job cannot be cleaned up directly in drm_writeback_signal_completion,
> > + * because it may be called in interrupt context. Dropping the framebuffer
> > + * reference can sleep, and so the cleanup is deferred to a workqueue.
> > + */
> > +static void cleanup_work(struct work_struct *work)
> > +{
> > +	struct drm_writeback_job *job = container_of(work,
> > +						     struct drm_writeback_job,
> > +						     cleanup_work);
> > +	drm_writeback_cleanup_job(job);
> 
> TIL that you can free work while using it.

Was not aware that it is possible. I can update the code to do it.

Thanks for the feedback and review!

Best regards,
Liviu

> 
> > +}
> > +
> > +/**
> > + * drm_writeback_signal_completion - Signal the completion of a writeback job
> > + * @wb_connector: The writeback connector whose job is complete
> > + *
> > + * Drivers should call this to signal the completion of a previously queued
> > + * writeback job. It should be called as soon as possible after the hardware
> > + * has finished writing, and may be called from interrupt context.
> > + * It is the driver's responsibility to ensure that for a given connector, the
> > + * hardware completes writeback jobs in the same order as they are queued.
> > + *
> > + * Unless the driver is holding its own reference to the framebuffer, it must
> > + * not be accessed after calling this function.
> > + *
> > + * See also: drm_writeback_queue_job()
> > + */
> > +void
> > +drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector)
> > +{
> > +	unsigned long flags;
> > +	struct drm_writeback_job *job;
> > +
> > +	spin_lock_irqsave(&wb_connector->job_lock, flags);
> > +	job = list_first_entry_or_null(&wb_connector->job_queue,
> > +				       struct drm_writeback_job,
> > +				       list_entry);
> > +	if (job)
> > +		list_del(&job->list_entry);
> > +	spin_unlock_irqrestore(&wb_connector->job_lock, flags);
> > +
> > +	if (WARN_ON(!job))
> > +		return;
> > +
> > +	INIT_WORK(&job->cleanup_work, cleanup_work);
> > +	queue_work(system_long_wq, &job->cleanup_work);
> > +}
> > +EXPORT_SYMBOL(drm_writeback_signal_completion);
> > diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
> > index cf13842a6dbd..d7b0263cc5cf 100644
> > --- a/include/drm/drm_atomic.h
> > +++ b/include/drm/drm_atomic.h
> > @@ -594,6 +594,9 @@ void drm_atomic_set_fence_for_plane(struct drm_plane_state *plane_state,
> >  int __must_check
> >  drm_atomic_set_crtc_for_connector(struct drm_connector_state *conn_state,
> >  				  struct drm_crtc *crtc);
> > +int drm_atomic_set_writeback_fb_for_connector(
> > +		struct drm_connector_state *conn_state,
> > +		struct drm_framebuffer *fb);
> >  int __must_check
> >  drm_atomic_add_affected_connectors(struct drm_atomic_state *state,
> >  				   struct drm_crtc *crtc);
> > diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
> > index 758a176e7b57..8701ebcc68b3 100644
> > --- a/include/drm/drm_connector.h
> > +++ b/include/drm/drm_connector.h
> > @@ -425,6 +425,19 @@ struct drm_connector_state {
> >  	 * protection. This is most commonly used for HDCP.
> >  	 */
> >  	unsigned int content_protection;
> > +
> > +	/**
> > +	 * @writeback_job: Writeback job for writeback connectors
> > +	 *
> > +	 * Holds the framebuffer for a writeback connector. As the writeback
> > +	 * completion may be asynchronous to the normal commit cycle, the
> > +	 * writeback job lifetime is managed separately from the normal atomic
> > +	 * state by this object.
> > +	 *
> > +	 * See also: drm_writeback_queue_job() and
> > +	 * drm_writeback_signal_completion()
> > +	 */
> > +	struct drm_writeback_job *writeback_job;
> >  };
> >  
> >  /**
> > diff --git a/include/drm/drm_mode_config.h b/include/drm/drm_mode_config.h
> > index 7569f22ffef6..c012e1148ec0 100644
> > --- a/include/drm/drm_mode_config.h
> > +++ b/include/drm/drm_mode_config.h
> > @@ -779,6 +779,20 @@ struct drm_mode_config {
> >  	 */
> >  	struct drm_property *panel_orientation_property;
> >  
> > +	/**
> > +	 * @writeback_fb_id_property: Property for writeback connectors, storing
> > +	 * the ID of the output framebuffer.
> > +	 * See also: drm_writeback_connector_init()
> > +	 */
> > +	struct drm_property *writeback_fb_id_property;
> > +	/**
> > +	 * @writeback_pixel_formats_property: Property for writeback connectors,
> > +	 * storing an array of the supported pixel formats for the writeback
> > +	 * engine (read-only).
> > +	 * See also: drm_writeback_connector_init()
> > +	 */
> > +	struct drm_property *writeback_pixel_formats_property;
> > +
> >  	/* dumb ioctl parameters */
> >  	uint32_t preferred_depth, prefer_shadow;
> >  
> > diff --git a/include/drm/drm_modeset_helper_vtables.h b/include/drm/drm_modeset_helper_vtables.h
> > index 3e76ca805b0f..97d3a810bc85 100644
> > --- a/include/drm/drm_modeset_helper_vtables.h
> > +++ b/include/drm/drm_modeset_helper_vtables.h
> > @@ -974,6 +974,17 @@ struct drm_connector_helper_funcs {
> >  	 */
> >  	int (*atomic_check)(struct drm_connector *connector,
> >  			    struct drm_connector_state *state);
> > +
> > +	/**
> > +	 * @atomic_commit:
> > +	 *
> > +	 * For write-back connectors, this is the point to commit the write-
> > +	 * back job to hw.
> > +	 *
> > +	 * This callback is used by the atomic modeset helpers.
> > +	 */
> > +	void (*atomic_commit)(struct drm_connector *connector,
> > +			      struct drm_writeback_job *writeback_job);
> >  };
> >  
> >  /**
> > diff --git a/include/drm/drm_writeback.h b/include/drm/drm_writeback.h
> > new file mode 100644
> > index 000000000000..0bb95fd4907d
> > --- /dev/null
> > +++ b/include/drm/drm_writeback.h
> > @@ -0,0 +1,89 @@
> > +/*
> > + * (C) COPYRIGHT 2016 ARM Limited. All rights reserved.
> > + * Author: Brian Starkey <brian.starkey@arm.com>
> > + *
> > + * This program is free software and is provided to you under the terms of the
> > + * GNU General Public License version 2 as published by the Free Software
> > + * Foundation, and any use by you of this program is subject to the terms
> > + * of such GNU licence.
> > + */
> > +
> > +#ifndef __DRM_WRITEBACK_H__
> > +#define __DRM_WRITEBACK_H__
> > +#include <drm/drm_connector.h>
> > +#include <drm/drm_encoder.h>
> > +#include <linux/workqueue.h>
> > +
> > +struct drm_writeback_connector {
> > +	struct drm_connector base;
> > +
> > +	/**
> > +	 * @encoder: Internal encoder used by the connector to fulfill
> > +	 * the DRM framework requirements. The users of the
> > +	 * @drm_writeback_connector control the behaviour of the @encoder
> > +	 * by passing the @enc_funcs parameter to drm_writeback_connector_init()
> > +	 * function.
> > +	 */
> > +	struct drm_encoder encoder;
> > +
> > +	/**
> > +	 * @pixel_formats_blob_ptr:
> > +	 *
> > +	 * DRM blob property data for the pixel formats list on writeback
> > +	 * connectors
> > +	 * See also drm_writeback_connector_init()
> > +	 */
> > +	struct drm_property_blob *pixel_formats_blob_ptr;
> > +
> > +	/** @job_lock: Protects job_queue */
> > +	spinlock_t job_lock;
> > +
> > +	/**
> > +	 * @job_queue:
> > +	 *
> > +	 * Holds a list of a connector's writeback jobs; the last item is the
> > +	 * most recent. The first item may be either waiting for the hardware
> > +	 * to begin writing, or currently being written.
> > +	 *
> > +	 * See also: drm_writeback_queue_job() and
> > +	 * drm_writeback_signal_completion()
> > +	 */
> > +	struct list_head job_queue;
> > +};
> > +#define to_wb_connector(x) container_of(x, struct drm_writeback_connector, base)
> > +
> > +struct drm_writeback_job {
> > +	/**
> > +	 * @cleanup_work:
> > +	 *
> > +	 * Used to allow drm_writeback_signal_completion to defer dropping the
> > +	 * framebuffer reference to a workqueue.
> > +	 */
> > +	struct work_struct cleanup_work;
> > +	/**
> > +	 * @list_entry:
> > +	 *
> > +	 * List item for the connector's @job_queue
> > +	 */
> > +	struct list_head list_entry;
> > +	/**
> > +	 * @fb:
> > +	 *
> > +	 * Framebuffer to be written to by the writeback connector. Do not set
> > +	 * directly, use drm_atomic_set_writeback_fb_for_connector()
> > +	 */
> > +	struct drm_framebuffer *fb;
> > +};
> > +
> > +int drm_writeback_connector_init(struct drm_device *dev,
> > +				 struct drm_writeback_connector *wb_connector,
> > +				 const struct drm_connector_funcs *con_funcs,
> > +				 const struct drm_encoder_helper_funcs *enc_helper_funcs,
> > +				 const u32 *formats, int n_formats);
> > +
> > +void drm_writeback_queue_job(struct drm_writeback_connector *wb_connector,
> > +			     struct drm_writeback_job *job);
> > +
> > +void drm_writeback_cleanup_job(struct drm_writeback_job *job);
> > +void drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector);
> > +#endif
> > diff --git a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h
> > index 2c575794fb52..7b47e184e95e 100644
> > --- a/include/uapi/drm/drm_mode.h
> > +++ b/include/uapi/drm/drm_mode.h
> > @@ -338,6 +338,7 @@ enum drm_mode_subconnector {
> >  #define DRM_MODE_CONNECTOR_VIRTUAL      15
> >  #define DRM_MODE_CONNECTOR_DSI		16
> >  #define DRM_MODE_CONNECTOR_DPI		17
> > +#define DRM_MODE_CONNECTOR_WRITEBACK	18
> >  
> >  struct drm_mode_get_connector {
> >  
> > -- 
> > 2.14.3
> > 
> 
> -- 
> Sean Paul, Software Engineer, Google / Chromium OS

-- 
====================
| I would like to |
| fix the world,  |
| but they're not |
| giving me the   |
 \ source code!  /
  ---------------
    ¯\_(ツ)_/¯
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [RFC 1/4] drm: Add writeback connector type
@ 2018-02-23 16:21           ` Liviu Dudau
  0 siblings, 0 replies; 41+ messages in thread
From: Liviu Dudau @ 2018-02-23 16:21 UTC (permalink / raw)
  To: Sean Paul
  Cc: Rob Clark, dri-devel, freedreno, linux-arm-msm, Brian Starkey,
	Mihail Atanassov, Gustavo Padovan, Maarten Lankhorst,
	David Airlie, Jonathan Corbet, linux-doc, linux-kernel

On Fri, Feb 23, 2018 at 10:59:35AM -0500, Sean Paul wrote:
> On Fri, Feb 23, 2018 at 08:17:51AM -0500, Rob Clark wrote:
> > From: Brian Starkey <brian.starkey@arm.com>
> > 
> > Writeback connectors represent writeback engines which can write the
> > CRTC output to a memory framebuffer. Add a writeback connector type and
> > related support functions.
> > 
> > Drivers should initialize a writeback connector with
> > drm_writeback_connector_init() which takes care of setting up all the
> > writeback-specific details on top of the normal functionality of
> > drm_connector_init().
> > 
> > Writeback connectors have a WRITEBACK_FB_ID property, used to set the
> > output framebuffer, and a WRITEBACK_PIXEL_FORMATS blob used to expose the
> > supported writeback formats to userspace.
> > 
> > When a framebuffer is attached to a writeback connector with the
> > WRITEBACK_FB_ID property, it is used only once (for the commit in which
> > it was included), and userspace can never read back the value of
> > WRITEBACK_FB_ID. WRITEBACK_FB_ID can only be set if the connector is
> > attached to a CRTC.
> > 
> > Changes since v1:
> >  - Added drm_writeback.c + documentation
> >  - Added helper to initialize writeback connector in one go
> >  - Added core checks
> >  - Squashed into a single commit
> >  - Dropped the client cap
> >  - Writeback framebuffers are no longer persistent
> > 
> > Changes since v2:
> >  Daniel Vetter:
> >  - Subclass drm_connector to drm_writeback_connector
> >  - Relax check to allow CRTC to be set without an FB
> >  - Add some writeback_ prefixes
> >  - Drop PIXEL_FORMATS_SIZE property, as it was unnecessary
> >  Gustavo Padovan:
> >  - Add drm_writeback_job to handle writeback signalling centrally
> > 
> > Changes since v3:
> >  - Rebased
> >  - Rename PIXEL_FORMATS -> WRITEBACK_PIXEL_FORMATS
> > 
> > Changes since v4:
> >  - Added atomic_commit() vfunc to connector helper funcs, so that
> >    writeback jobs are committed from atomic helpers
> > 
> > Signed-off-by: Brian Starkey <brian.starkey@arm.com>
> > [rebased and fixed conflicts]
> > Signed-off-by: Mihail Atanassov <mihail.atanassov@arm.com>
> > Signed-off-by: Liviu Dudau <liviu.dudau@arm.com>
> > [rebased and added atomic_commit() vfunc for writeback jobs]
> > Signed-off-by: Rob Clark <robdclark@gmail.com>
> > ---
> >  Documentation/gpu/drm-kms.rst            |   9 ++
> >  drivers/gpu/drm/Makefile                 |   2 +-
> >  drivers/gpu/drm/drm_atomic.c             | 130 ++++++++++++++++
> >  drivers/gpu/drm/drm_atomic_helper.c      |  30 ++++
> >  drivers/gpu/drm/drm_connector.c          |   4 +-
> >  drivers/gpu/drm/drm_writeback.c          | 257 +++++++++++++++++++++++++++++++
> >  include/drm/drm_atomic.h                 |   3 +
> >  include/drm/drm_connector.h              |  13 ++
> >  include/drm/drm_mode_config.h            |  14 ++
> >  include/drm/drm_modeset_helper_vtables.h |  11 ++
> >  include/drm/drm_writeback.h              |  89 +++++++++++
> >  include/uapi/drm/drm_mode.h              |   1 +
> >  12 files changed, 561 insertions(+), 2 deletions(-)
> >  create mode 100644 drivers/gpu/drm/drm_writeback.c
> >  create mode 100644 include/drm/drm_writeback.h
> > 
> > diff --git a/Documentation/gpu/drm-kms.rst b/Documentation/gpu/drm-kms.rst
> > index 2dcf5b42015d..e7590dd2f71e 100644
> > --- a/Documentation/gpu/drm-kms.rst
> > +++ b/Documentation/gpu/drm-kms.rst
> > @@ -370,6 +370,15 @@ Connector Functions Reference
> >  .. kernel-doc:: drivers/gpu/drm/drm_connector.c
> >     :export:
> >  
> > +Writeback Connectors
> > +--------------------
> > +
> > +.. kernel-doc:: drivers/gpu/drm/drm_writeback.c
> > +  :doc: overview
> > +
> > +.. kernel-doc:: drivers/gpu/drm/drm_writeback.c
> > +  :export:
> > +
> >  Encoder Abstraction
> >  ===================
> >  
> > diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> > index 50093ff4479b..3d708959b224 100644
> > --- a/drivers/gpu/drm/Makefile
> > +++ b/drivers/gpu/drm/Makefile
> > @@ -18,7 +18,7 @@ drm-y       :=	drm_auth.o drm_bufs.o drm_cache.o \
> >  		drm_encoder.o drm_mode_object.o drm_property.o \
> >  		drm_plane.o drm_color_mgmt.o drm_print.o \
> >  		drm_dumb_buffers.o drm_mode_config.o drm_vblank.o \
> > -		drm_syncobj.o drm_lease.o
> > +		drm_syncobj.o drm_lease.o drm_writeback.o
> >  
> >  drm-$(CONFIG_DRM_LIB_RANDOM) += lib/drm_random.o
> >  drm-$(CONFIG_DRM_VM) += drm_vm.o
> > diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
> > index 46733d534587..019f131fe8be 100644
> > --- a/drivers/gpu/drm/drm_atomic.c
> > +++ b/drivers/gpu/drm/drm_atomic.c
> > @@ -30,6 +30,7 @@
> >  #include <drm/drm_atomic.h>
> >  #include <drm/drm_mode.h>
> >  #include <drm/drm_print.h>
> > +#include <drm/drm_writeback.h>
> >  #include <linux/sync_file.h>
> >  
> >  #include "drm_crtc_internal.h"
> > @@ -638,6 +639,46 @@ static void drm_atomic_crtc_print_state(struct drm_printer *p,
> >  		crtc->funcs->atomic_print_state(p, state);
> >  }
> >  
> > +/**
> > + * drm_atomic_connector_check - check connector state
> > + * @connector: connector to check
> > + * @state: connector state to check
> > + *
> > + * Provides core sanity checks for connector state.
> > + *
> > + * RETURNS:
> > + * Zero on success, error code on failure
> > + */
> > +static int drm_atomic_connector_check(struct drm_connector *connector,
> > +		struct drm_connector_state *state)
> > +{
> > +	struct drm_crtc_state *crtc_state;
> > +	struct drm_writeback_job *writeback_job = state->writeback_job;
> > +
> > +	if ((connector->connector_type != DRM_MODE_CONNECTOR_WRITEBACK) ||
> > +	    !writeback_job)
> > +		return 0;
> > +
> > +	if (writeback_job->fb && !state->crtc) {
> > +		DRM_DEBUG_ATOMIC("[CONNECTOR:%d:%s] framebuffer without CRTC\n",
> > +				 connector->base.id, connector->name);
> > +		return -EINVAL;
> > +	}
> > +
> > +	if (state->crtc)
> > +		crtc_state = drm_atomic_get_existing_crtc_state(state->state,
> > +								state->crtc);
> > +
> > +	if (writeback_job->fb && !crtc_state->active) {
> 
> Checking for writeback_job->fb is redundant here and above since it's checked
> first thing as early exit criteria.

I somehow don't see that check. We check if (!writebac_job) and then all
other checks are logic ands with other conditions, so I think the check
here is still relevant.

> 
> > +		DRM_DEBUG_ATOMIC("[CONNECTOR:%d:%s] has framebuffer, but [CRTC:%d] is off\n",
> > +				 connector->base.id, connector->name,
> > +				 state->crtc->base.id);
> > +		return -EINVAL;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> >  /**
> >   * drm_atomic_get_plane_state - get plane state
> >   * @state: global atomic state object
> > @@ -1230,6 +1271,12 @@ static int drm_atomic_connector_set_property(struct drm_connector *connector,
> >  			return -EINVAL;
> >  		}
> >  		state->content_protection = val;
> > +	} else if (property == config->writeback_fb_id_property) {
> > +		struct drm_framebuffer *fb = drm_framebuffer_lookup(dev, NULL, val);
> > +		int ret = drm_atomic_set_writeback_fb_for_connector(state, fb);
> > +		if (fb)
> > +			drm_framebuffer_unreference(fb);
> > +		return ret;
> >  	} else if (connector->funcs->atomic_set_property) {
> >  		return connector->funcs->atomic_set_property(connector,
> >  				state, property, val);
> > @@ -1311,6 +1358,9 @@ drm_atomic_connector_get_property(struct drm_connector *connector,
> >  		*val = state->scaling_mode;
> >  	} else if (property == connector->content_protection_property) {
> >  		*val = state->content_protection;
> > +	} else if (property == config->writeback_fb_id_property) {
> > +		/* Writeback framebuffer is one-shot, write and forget */
> > +		*val = 0;
> >  	} else if (connector->funcs->atomic_get_property) {
> >  		return connector->funcs->atomic_get_property(connector,
> >  				state, property, val);
> > @@ -1518,6 +1568,75 @@ drm_atomic_set_crtc_for_connector(struct drm_connector_state *conn_state,
> >  }
> >  EXPORT_SYMBOL(drm_atomic_set_crtc_for_connector);
> >  
> > +/*
> > + * drm_atomic_get_writeback_job - return or allocate a writeback job
> > + * @conn_state: Connector state to get the job for
> > + *
> > + * Writeback jobs have a different lifetime to the atomic state they are
> > + * associated with. This convenience function takes care of allocating a job
> > + * if there isn't yet one associated with the connector state, otherwise
> > + * it just returns the existing job.
> > + *
> > + * Returns: The writeback job for the given connector state
> > + */
> > +static struct drm_writeback_job *
> > +drm_atomic_get_writeback_job(struct drm_connector_state *conn_state)
> > +{
> > +	WARN_ON(conn_state->connector->connector_type !=
> > +		DRM_MODE_CONNECTOR_WRITEBACK);
> > +
> > +	if (!conn_state->writeback_job)
> > +		conn_state->writeback_job =
> > +			kzalloc(sizeof(*conn_state->writeback_job), GFP_KERNEL);
> > +
> > +	return conn_state->writeback_job;
> > +}
> > +
> > +/**
> > + * drm_atomic_set_writeback_fb_for_connector - set writeback framebuffer
> > + * @conn_state: atomic state object for the connector
> > + * @fb: fb to use for the connector
> > + *
> > + * This is used to set the framebuffer for a writeback connector, which outputs
> > + * to a buffer instead of an actual physical connector.
> > + * Changing the assigned framebuffer requires us to grab a reference to the new
> > + * fb and drop the reference to the old fb, if there is one. This function
> > + * takes care of all these details besides updating the pointer in the
> > + * state object itself.
> > + *
> > + * Note: The only way conn_state can already have an fb set is if the commit
> > + * sets the property more than once.
> > + *
> > + * See also: drm_writeback_connector_init()
> > + *
> > + * Returns: 0 on success
> > + */
> > +int drm_atomic_set_writeback_fb_for_connector(
> > +		struct drm_connector_state *conn_state,
> > +		struct drm_framebuffer *fb)
> > +{
> > +	struct drm_writeback_job *job =
> > +		drm_atomic_get_writeback_job(conn_state);
> > +	if (!job)
> > +		return -ENOMEM;
> > +
> > +	if (job->fb)
> > +		drm_framebuffer_unreference(job->fb);
> > +	if (fb)
> > +		drm_framebuffer_reference(fb);
> > +	job->fb = fb;
> > +
> > +	if (fb)
> > +		DRM_DEBUG_ATOMIC("Set [FB:%d] for connector state %p\n",
> > +				 fb->base.id, conn_state);
> > +	else
> > +		DRM_DEBUG_ATOMIC("Set [NOFB] for connector state %p\n",
> > +				 conn_state);
> > +
> > +	return 0;
> > +}
> > +EXPORT_SYMBOL(drm_atomic_set_writeback_fb_for_connector);
> > +
> >  /**
> >   * drm_atomic_add_affected_connectors - add connectors for crtc
> >   * @state: atomic state
> > @@ -1636,6 +1755,8 @@ int drm_atomic_check_only(struct drm_atomic_state *state)
> >  	struct drm_plane_state *plane_state;
> >  	struct drm_crtc *crtc;
> >  	struct drm_crtc_state *crtc_state;
> > +	struct drm_connector *conn;
> > +	struct drm_connector_state *conn_state;
> >  	int i, ret = 0;
> >  
> >  	DRM_DEBUG_ATOMIC("checking %p\n", state);
> > @@ -1658,6 +1779,15 @@ int drm_atomic_check_only(struct drm_atomic_state *state)
> >  		}
> >  	}
> >  
> > +	for_each_new_connector_in_state(state, conn, conn_state, i) {
> > +		ret = drm_atomic_connector_check(conn, conn_state);
> > +		if (ret) {
> > +			DRM_DEBUG_ATOMIC("[CONNECTOR:%d:%s] atomic core check failed\n",
> > +					 conn->base.id, conn->name);
> > +			return ret;
> > +		}
> > +	}
> > +
> >  	if (config->funcs->atomic_check)
> >  		ret = config->funcs->atomic_check(state->dev, state);
> >  
> > diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c
> > index ae3cbfe9e01c..12b910755d84 100644
> > --- a/drivers/gpu/drm/drm_atomic_helper.c
> > +++ b/drivers/gpu/drm/drm_atomic_helper.c
> > @@ -30,6 +30,7 @@
> >  #include <drm/drm_plane_helper.h>
> >  #include <drm/drm_crtc_helper.h>
> >  #include <drm/drm_atomic_helper.h>
> > +#include <drm/drm_writeback.h>
> >  #include <linux/dma-fence.h>
> >  
> >  #include "drm_crtc_helper_internal.h"
> > @@ -1159,6 +1160,27 @@ void drm_atomic_helper_commit_modeset_disables(struct drm_device *dev,
> >  }
> >  EXPORT_SYMBOL(drm_atomic_helper_commit_modeset_disables);
> >  
> > +static void commit_writebacks(struct drm_device *dev, struct drm_atomic_state *old_state)
> > +{
> > +	struct drm_connector *connector;
> > +	struct drm_connector_state *new_conn_state;
> > +	int i;
> > +
> > +	for_each_new_connector_in_state(old_state, connector, new_conn_state, i) {
> > +		const struct drm_connector_helper_funcs *funcs;
> > +
> > +		funcs = connector->helper_private;
> > +
> > +		if (new_conn_state->writeback_job &&
> > +		    new_conn_state->writeback_job->fb) {
> > +			WARN_ON(connector->connector_type !=
> > +				DRM_MODE_CONNECTOR_WRITEBACK);
> > +			funcs->atomic_commit(connector,
> > +					     new_conn_state->writeback_job);
> > +		}
> > +	}
> > +}
> > +
> >  /**
> >   * drm_atomic_helper_commit_modeset_enables - modeset commit to enable outputs
> >   * @dev: DRM device
> > @@ -1238,6 +1260,8 @@ void drm_atomic_helper_commit_modeset_enables(struct drm_device *dev,
> >  
> >  		drm_bridge_enable(encoder->bridge);
> >  	}
> > +
> > +	commit_writebacks(dev, old_state);
> >  }
> >  EXPORT_SYMBOL(drm_atomic_helper_commit_modeset_enables);
> >  
> > @@ -3627,6 +3651,9 @@ __drm_atomic_helper_connector_duplicate_state(struct drm_connector *connector,
> >  	if (state->crtc)
> >  		drm_connector_get(connector);
> >  	state->commit = NULL;
> > +
> > +	/* Don't copy over a writeback job, they are used only once */
> > +	state->writeback_job = NULL;
> >  }
> >  EXPORT_SYMBOL(__drm_atomic_helper_connector_duplicate_state);
> >  
> > @@ -3756,6 +3783,9 @@ __drm_atomic_helper_connector_destroy_state(struct drm_connector_state *state)
> >  
> >  	if (state->commit)
> >  		drm_crtc_commit_put(state->commit);
> > +
> > +	if (state->writeback_job)
> > +		drm_writeback_cleanup_job(state->writeback_job);
> >  }
> >  EXPORT_SYMBOL(__drm_atomic_helper_connector_destroy_state);
> >  
> > diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c
> > index 16b9c3810af2..add47f06ae70 100644
> > --- a/drivers/gpu/drm/drm_connector.c
> > +++ b/drivers/gpu/drm/drm_connector.c
> > @@ -87,6 +87,7 @@ static struct drm_conn_prop_enum_list drm_connector_enum_list[] = {
> >  	{ DRM_MODE_CONNECTOR_VIRTUAL, "Virtual" },
> >  	{ DRM_MODE_CONNECTOR_DSI, "DSI" },
> >  	{ DRM_MODE_CONNECTOR_DPI, "DPI" },
> > +	{ DRM_MODE_CONNECTOR_WRITEBACK, "Writeback" },
> >  };
> >  
> >  void drm_connector_ida_init(void)
> > @@ -249,7 +250,8 @@ int drm_connector_init(struct drm_device *dev,
> >  	config->num_connector++;
> >  	spin_unlock_irq(&config->connector_list_lock);
> >  
> > -	if (connector_type != DRM_MODE_CONNECTOR_VIRTUAL)
> > +	if (connector_type != DRM_MODE_CONNECTOR_VIRTUAL &&
> > +	    connector_type != DRM_MODE_CONNECTOR_WRITEBACK)
> >  		drm_object_attach_property(&connector->base,
> >  					      config->edid_property,
> >  					      0);
> > diff --git a/drivers/gpu/drm/drm_writeback.c b/drivers/gpu/drm/drm_writeback.c
> > new file mode 100644
> > index 000000000000..da61f929cbc3
> > --- /dev/null
> > +++ b/drivers/gpu/drm/drm_writeback.c
> > @@ -0,0 +1,257 @@
> > +/*
> > + * (C) COPYRIGHT 2016 ARM Limited. All rights reserved.
> > + * Author: Brian Starkey <brian.starkey@arm.com>
> > + *
> > + * This program is free software and is provided to you under the terms of the
> > + * GNU General Public License version 2 as published by the Free Software
> > + * Foundation, and any use by you of this program is subject to the terms
> > + * of such GNU licence.
> > + */
> > +
> > +#include <drm/drm_crtc.h>
> > +#include <drm/drm_modeset_helper_vtables.h>
> > +#include <drm/drm_property.h>
> > +#include <drm/drm_writeback.h>
> > +#include <drm/drmP.h>
> > +
> > +/**
> > + * DOC: overview
> > + *
> > + * Writeback connectors are used to expose hardware which can write the output
> > + * from a CRTC to a memory buffer. They are used and act similarly to other
> > + * types of connectors, with some important differences:
> > + *  - Writeback connectors don't provide a way to output visually to the user.
> > + *  - Writeback connectors should always report as "disconnected" (so that
> > + *    clients which don't understand them will ignore them).
> 
> Have we considered hiding writeback behind a client cap instead?

Yes, changelog says that the client cap was dropped in v2 based on
feedback (as far as I remember). 

> 
> > + *  - Writeback connectors don't have EDID.
> > + *
> > + * A framebuffer may only be attached to a writeback connector when the
> > + * connector is attached to a CRTC. The WRITEBACK_FB_ID property which sets the
> > + * framebuffer applies only to a single commit (see below). A framebuffer may
> > + * not be attached while the CRTC is off.
> > + *
> > + * Writeback connectors have some additional properties, which userspace
> > + * can use to query and control them:
> > + *
> > + *  "WRITEBACK_FB_ID":
> > + *	Write-only object property storing a DRM_MODE_OBJECT_FB: it stores the
> > + *	framebuffer to be written by the writeback connector. This property is
> > + *	similar to the FB_ID property on planes, but will always read as zero
> > + *	and is not preserved across commits.
> > + *	Userspace must set this property to an output buffer every time it
> > + *	wishes the buffer to get filled.
> > + *
> > + *  "WRITEBACK_PIXEL_FORMATS":
> > + *	Immutable blob property to store the supported pixel formats table. The
> > + *	data is an array of u32 DRM_FORMAT_* fourcc values.
> > + *	Userspace can use this blob to find out what pixel formats are supported
> > + *	by the connector's writeback engine.
> > + */
> > +
> > +static bool create_writeback_properties(struct drm_device *dev)
> > +{
> > +	struct drm_property *prop;
> > +
> > +	if (!dev->mode_config.writeback_fb_id_property) {
> 
> Somewhat an aside, but we should probably come up with more formal rules on
> where to stick properties. We have some in drm_connector, which I interpret as
> "connector-only properties", but we also have a bunch in mode_config.
> 
> For optional properties which will generally only apply to one connector on a
> system, I'd be inclined to put it in connector. 

I can move the writeback-related properties into the connector in the
next series.


> 
> > +		prop = drm_property_create_object(dev, DRM_MODE_PROP_ATOMIC,
> > +						  "WRITEBACK_FB_ID",
> > +						  DRM_MODE_OBJECT_FB);
> > +		if (!prop)
> > +			return false;
> > +		dev->mode_config.writeback_fb_id_property = prop;
> > +	}
> > +
> > +	if (!dev->mode_config.writeback_pixel_formats_property) {
> > +		prop = drm_property_create(dev, DRM_MODE_PROP_BLOB | DRM_MODE_PROP_IMMUTABLE,
> > +					   "WRITEBACK_PIXEL_FORMATS", 0);
> 
> Does it make sense to expose the formats if u/s doesn't support atomic and can't
> use writeback? Perhaps this should also be DRM_MODE_PROP_ATOMIC.

Agree, probably all the writeback properties should only be available if
the client selects DRM_MODE_PROP_ATOMIC.

> 
> 
> > +		if (!prop)
> > +			return false;
> > +		dev->mode_config.writeback_pixel_formats_property = prop;
> > +	}
> > +
> > +	return true;
> > +}
> > +
> > +static const struct drm_encoder_funcs drm_writeback_encoder_funcs = {
> > +	.destroy = drm_encoder_cleanup,
> > +};
> > +
> > +/**
> > + * drm_writeback_connector_init - Initialize a writeback connector and its properties
> > + * @dev: DRM device
> > + * @wb_connector: Writeback connector to initialize
> > + * @con_funcs: Connector funcs vtable
> > + * @enc_helper_funcs: Encoder helper funcs vtable to be used by the internal encoder
> > + * @formats: Array of supported pixel formats for the writeback engine
> > + * @n_formats: Length of the formats array
> > + *
> > + * This function creates the writeback-connector-specific properties if they
> > + * have not been already created, initializes the connector as
> > + * type DRM_MODE_CONNECTOR_WRITEBACK, and correctly initializes the property
> > + * values. It will also create an internal encoder associated with the
> > + * drm_writeback_connector and set it to use the @enc_helper_funcs vtable for
> > + * the encoder helper.
> > + *
> > + * Drivers should always use this function instead of drm_connector_init() to
> > + * set up writeback connectors.
> > + *
> > + * Returns: 0 on success, or a negative error code
> > + */
> > +int drm_writeback_connector_init(struct drm_device *dev,
> > +				 struct drm_writeback_connector *wb_connector,
> > +				 const struct drm_connector_funcs *con_funcs,
> > +				 const struct drm_encoder_helper_funcs *enc_helper_funcs,
> > +				 const u32 *formats, int n_formats)
> > +{
> > +	int ret;
> > +	struct drm_property_blob *blob;
> > +	struct drm_connector *connector = &wb_connector->base;
> > +	struct drm_mode_config *config = &dev->mode_config;
> > +
> > +	if (!create_writeback_properties(dev))
> > +		return -EINVAL;
> > +
> > +	blob = drm_property_create_blob(dev, n_formats * sizeof(*formats),
> > +					formats);
> > +	if (IS_ERR(blob))
> > +		return PTR_ERR(blob);
> > +
> > +	drm_encoder_helper_add(&wb_connector->encoder, enc_helper_funcs);
> > +	ret = drm_encoder_init(dev, &wb_connector->encoder,
> > +			       &drm_writeback_encoder_funcs,
> > +			       DRM_MODE_ENCODER_VIRTUAL, NULL);
> > +	if (ret)
> > +		goto fail;
> > +
> > +	connector->interlace_allowed = 0;
> > +
> > +	ret = drm_connector_init(dev, connector, con_funcs,
> > +				 DRM_MODE_CONNECTOR_WRITEBACK);
> > +	if (ret)
> > +		goto connector_fail;
> > +
> > +	ret = drm_mode_connector_attach_encoder(connector,
> > +						&wb_connector->encoder);
> > +	if (ret)
> > +		goto attach_fail;
> > +
> > +	INIT_LIST_HEAD(&wb_connector->job_queue);
> > +	spin_lock_init(&wb_connector->job_lock);
> > +
> > +	drm_object_attach_property(&connector->base,
> > +				   config->writeback_fb_id_property, 0);
> > +
> > +	drm_object_attach_property(&connector->base,
> > +				   config->writeback_pixel_formats_property,
> > +				   blob->base.id);
> > +	wb_connector->pixel_formats_blob_ptr = blob;
> > +
> > +	return 0;
> > +
> > +attach_fail:
> > +	drm_connector_cleanup(connector);
> > +connector_fail:
> > +	drm_encoder_cleanup(&wb_connector->encoder);
> > +fail:
> > +	drm_property_unreference_blob(blob);
> > +	return ret;
> > +}
> > +EXPORT_SYMBOL(drm_writeback_connector_init);
> > +
> > +/**
> > + * drm_writeback_queue_job - Queue a writeback job for later signalling
> > + * @wb_connector: The writeback connector to queue a job on
> > + * @job: The job to queue
> > + *
> > + * This function adds a job to the job_queue for a writeback connector. It
> > + * should be considered to take ownership of the writeback job, and so any other
> > + * references to the job must be cleared after calling this function.
> > + *
> > + * Drivers must ensure that for a given writeback connector, jobs are queued in
> > + * exactly the same order as they will be completed by the hardware (and
> > + * signaled via drm_writeback_signal_completion).
> > + *
> > + * For every call to drm_writeback_queue_job() there must be exactly one call to
> > + * drm_writeback_signal_completion()
> > + *
> > + * See also: drm_writeback_signal_completion()
> > + */
> > +void drm_writeback_queue_job(struct drm_writeback_connector *wb_connector,
> > +			     struct drm_writeback_job *job)
> > +{
> > +	unsigned long flags;
> > +
> > +	spin_lock_irqsave(&wb_connector->job_lock, flags);
> > +	list_add_tail(&job->list_entry, &wb_connector->job_queue);
> > +	spin_unlock_irqrestore(&wb_connector->job_lock, flags);
> > +}
> > +EXPORT_SYMBOL(drm_writeback_queue_job);
> > +
> > +/**
> > + * drm_writeback_cleanup_job - Cleanup and free a writeback job
> > + * @job: The writeback job to free
> > + *
> > + * Drops any references held by the writeback job, and frees the structure.
> > + */
> > +void drm_writeback_cleanup_job(struct drm_writeback_job *job)
> > +{
> > +	if (!job)
> > +		return;
> > +
> > +	if (job->fb)
> > +		drm_framebuffer_unreference(job->fb);
> > +	kfree(job);
> > +}
> > +EXPORT_SYMBOL(drm_writeback_cleanup_job);
> > +
> > +/*
> > + * @cleanup_work: deferred cleanup of a writeback job
> > + *
> > + * The job cannot be cleaned up directly in drm_writeback_signal_completion,
> > + * because it may be called in interrupt context. Dropping the framebuffer
> > + * reference can sleep, and so the cleanup is deferred to a workqueue.
> > + */
> > +static void cleanup_work(struct work_struct *work)
> > +{
> > +	struct drm_writeback_job *job = container_of(work,
> > +						     struct drm_writeback_job,
> > +						     cleanup_work);
> > +	drm_writeback_cleanup_job(job);
> 
> TIL that you can free work while using it.

Was not aware that it is possible. I can update the code to do it.

Thanks for the feedback and review!

Best regards,
Liviu

> 
> > +}
> > +
> > +/**
> > + * drm_writeback_signal_completion - Signal the completion of a writeback job
> > + * @wb_connector: The writeback connector whose job is complete
> > + *
> > + * Drivers should call this to signal the completion of a previously queued
> > + * writeback job. It should be called as soon as possible after the hardware
> > + * has finished writing, and may be called from interrupt context.
> > + * It is the driver's responsibility to ensure that for a given connector, the
> > + * hardware completes writeback jobs in the same order as they are queued.
> > + *
> > + * Unless the driver is holding its own reference to the framebuffer, it must
> > + * not be accessed after calling this function.
> > + *
> > + * See also: drm_writeback_queue_job()
> > + */
> > +void
> > +drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector)
> > +{
> > +	unsigned long flags;
> > +	struct drm_writeback_job *job;
> > +
> > +	spin_lock_irqsave(&wb_connector->job_lock, flags);
> > +	job = list_first_entry_or_null(&wb_connector->job_queue,
> > +				       struct drm_writeback_job,
> > +				       list_entry);
> > +	if (job)
> > +		list_del(&job->list_entry);
> > +	spin_unlock_irqrestore(&wb_connector->job_lock, flags);
> > +
> > +	if (WARN_ON(!job))
> > +		return;
> > +
> > +	INIT_WORK(&job->cleanup_work, cleanup_work);
> > +	queue_work(system_long_wq, &job->cleanup_work);
> > +}
> > +EXPORT_SYMBOL(drm_writeback_signal_completion);
> > diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
> > index cf13842a6dbd..d7b0263cc5cf 100644
> > --- a/include/drm/drm_atomic.h
> > +++ b/include/drm/drm_atomic.h
> > @@ -594,6 +594,9 @@ void drm_atomic_set_fence_for_plane(struct drm_plane_state *plane_state,
> >  int __must_check
> >  drm_atomic_set_crtc_for_connector(struct drm_connector_state *conn_state,
> >  				  struct drm_crtc *crtc);
> > +int drm_atomic_set_writeback_fb_for_connector(
> > +		struct drm_connector_state *conn_state,
> > +		struct drm_framebuffer *fb);
> >  int __must_check
> >  drm_atomic_add_affected_connectors(struct drm_atomic_state *state,
> >  				   struct drm_crtc *crtc);
> > diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
> > index 758a176e7b57..8701ebcc68b3 100644
> > --- a/include/drm/drm_connector.h
> > +++ b/include/drm/drm_connector.h
> > @@ -425,6 +425,19 @@ struct drm_connector_state {
> >  	 * protection. This is most commonly used for HDCP.
> >  	 */
> >  	unsigned int content_protection;
> > +
> > +	/**
> > +	 * @writeback_job: Writeback job for writeback connectors
> > +	 *
> > +	 * Holds the framebuffer for a writeback connector. As the writeback
> > +	 * completion may be asynchronous to the normal commit cycle, the
> > +	 * writeback job lifetime is managed separately from the normal atomic
> > +	 * state by this object.
> > +	 *
> > +	 * See also: drm_writeback_queue_job() and
> > +	 * drm_writeback_signal_completion()
> > +	 */
> > +	struct drm_writeback_job *writeback_job;
> >  };
> >  
> >  /**
> > diff --git a/include/drm/drm_mode_config.h b/include/drm/drm_mode_config.h
> > index 7569f22ffef6..c012e1148ec0 100644
> > --- a/include/drm/drm_mode_config.h
> > +++ b/include/drm/drm_mode_config.h
> > @@ -779,6 +779,20 @@ struct drm_mode_config {
> >  	 */
> >  	struct drm_property *panel_orientation_property;
> >  
> > +	/**
> > +	 * @writeback_fb_id_property: Property for writeback connectors, storing
> > +	 * the ID of the output framebuffer.
> > +	 * See also: drm_writeback_connector_init()
> > +	 */
> > +	struct drm_property *writeback_fb_id_property;
> > +	/**
> > +	 * @writeback_pixel_formats_property: Property for writeback connectors,
> > +	 * storing an array of the supported pixel formats for the writeback
> > +	 * engine (read-only).
> > +	 * See also: drm_writeback_connector_init()
> > +	 */
> > +	struct drm_property *writeback_pixel_formats_property;
> > +
> >  	/* dumb ioctl parameters */
> >  	uint32_t preferred_depth, prefer_shadow;
> >  
> > diff --git a/include/drm/drm_modeset_helper_vtables.h b/include/drm/drm_modeset_helper_vtables.h
> > index 3e76ca805b0f..97d3a810bc85 100644
> > --- a/include/drm/drm_modeset_helper_vtables.h
> > +++ b/include/drm/drm_modeset_helper_vtables.h
> > @@ -974,6 +974,17 @@ struct drm_connector_helper_funcs {
> >  	 */
> >  	int (*atomic_check)(struct drm_connector *connector,
> >  			    struct drm_connector_state *state);
> > +
> > +	/**
> > +	 * @atomic_commit:
> > +	 *
> > +	 * For write-back connectors, this is the point to commit the write-
> > +	 * back job to hw.
> > +	 *
> > +	 * This callback is used by the atomic modeset helpers.
> > +	 */
> > +	void (*atomic_commit)(struct drm_connector *connector,
> > +			      struct drm_writeback_job *writeback_job);
> >  };
> >  
> >  /**
> > diff --git a/include/drm/drm_writeback.h b/include/drm/drm_writeback.h
> > new file mode 100644
> > index 000000000000..0bb95fd4907d
> > --- /dev/null
> > +++ b/include/drm/drm_writeback.h
> > @@ -0,0 +1,89 @@
> > +/*
> > + * (C) COPYRIGHT 2016 ARM Limited. All rights reserved.
> > + * Author: Brian Starkey <brian.starkey@arm.com>
> > + *
> > + * This program is free software and is provided to you under the terms of the
> > + * GNU General Public License version 2 as published by the Free Software
> > + * Foundation, and any use by you of this program is subject to the terms
> > + * of such GNU licence.
> > + */
> > +
> > +#ifndef __DRM_WRITEBACK_H__
> > +#define __DRM_WRITEBACK_H__
> > +#include <drm/drm_connector.h>
> > +#include <drm/drm_encoder.h>
> > +#include <linux/workqueue.h>
> > +
> > +struct drm_writeback_connector {
> > +	struct drm_connector base;
> > +
> > +	/**
> > +	 * @encoder: Internal encoder used by the connector to fulfill
> > +	 * the DRM framework requirements. The users of the
> > +	 * @drm_writeback_connector control the behaviour of the @encoder
> > +	 * by passing the @enc_funcs parameter to drm_writeback_connector_init()
> > +	 * function.
> > +	 */
> > +	struct drm_encoder encoder;
> > +
> > +	/**
> > +	 * @pixel_formats_blob_ptr:
> > +	 *
> > +	 * DRM blob property data for the pixel formats list on writeback
> > +	 * connectors
> > +	 * See also drm_writeback_connector_init()
> > +	 */
> > +	struct drm_property_blob *pixel_formats_blob_ptr;
> > +
> > +	/** @job_lock: Protects job_queue */
> > +	spinlock_t job_lock;
> > +
> > +	/**
> > +	 * @job_queue:
> > +	 *
> > +	 * Holds a list of a connector's writeback jobs; the last item is the
> > +	 * most recent. The first item may be either waiting for the hardware
> > +	 * to begin writing, or currently being written.
> > +	 *
> > +	 * See also: drm_writeback_queue_job() and
> > +	 * drm_writeback_signal_completion()
> > +	 */
> > +	struct list_head job_queue;
> > +};
> > +#define to_wb_connector(x) container_of(x, struct drm_writeback_connector, base)
> > +
> > +struct drm_writeback_job {
> > +	/**
> > +	 * @cleanup_work:
> > +	 *
> > +	 * Used to allow drm_writeback_signal_completion to defer dropping the
> > +	 * framebuffer reference to a workqueue.
> > +	 */
> > +	struct work_struct cleanup_work;
> > +	/**
> > +	 * @list_entry:
> > +	 *
> > +	 * List item for the connector's @job_queue
> > +	 */
> > +	struct list_head list_entry;
> > +	/**
> > +	 * @fb:
> > +	 *
> > +	 * Framebuffer to be written to by the writeback connector. Do not set
> > +	 * directly, use drm_atomic_set_writeback_fb_for_connector()
> > +	 */
> > +	struct drm_framebuffer *fb;
> > +};
> > +
> > +int drm_writeback_connector_init(struct drm_device *dev,
> > +				 struct drm_writeback_connector *wb_connector,
> > +				 const struct drm_connector_funcs *con_funcs,
> > +				 const struct drm_encoder_helper_funcs *enc_helper_funcs,
> > +				 const u32 *formats, int n_formats);
> > +
> > +void drm_writeback_queue_job(struct drm_writeback_connector *wb_connector,
> > +			     struct drm_writeback_job *job);
> > +
> > +void drm_writeback_cleanup_job(struct drm_writeback_job *job);
> > +void drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector);
> > +#endif
> > diff --git a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h
> > index 2c575794fb52..7b47e184e95e 100644
> > --- a/include/uapi/drm/drm_mode.h
> > +++ b/include/uapi/drm/drm_mode.h
> > @@ -338,6 +338,7 @@ enum drm_mode_subconnector {
> >  #define DRM_MODE_CONNECTOR_VIRTUAL      15
> >  #define DRM_MODE_CONNECTOR_DSI		16
> >  #define DRM_MODE_CONNECTOR_DPI		17
> > +#define DRM_MODE_CONNECTOR_WRITEBACK	18
> >  
> >  struct drm_mode_get_connector {
> >  
> > -- 
> > 2.14.3
> > 
> 
> -- 
> Sean Paul, Software Engineer, Google / Chromium OS

-- 
====================
| I would like to |
| fix the world,  |
| but they're not |
| giving me the   |
 \ source code!  /
  ---------------
    ¯\_(ツ)_/¯

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [RFC 1/4] drm: Add writeback connector type
  2018-02-23 15:59         ` Sean Paul
@ 2018-02-23 16:25           ` Rob Clark
  -1 siblings, 0 replies; 41+ messages in thread
From: Rob Clark @ 2018-02-23 16:25 UTC (permalink / raw)
  To: Sean Paul
  Cc: Jonathan Corbet, Maarten Lankhorst, linux-arm-msm,
	linux-doc-u79uwXL29TY76Z2rM5mHXA, Liviu Dudau,
	Linux Kernel Mailing List, dri-devel, David Airlie,
	Gustavo Padovan, Mihail Atanassov, freedreno, Brian Starkey

On Fri, Feb 23, 2018 at 10:59 AM, Sean Paul <seanpaul@chromium.org> wrote:
>
> Have we considered hiding writeback behind a client cap instead?

It is kinda *almost* unneeded, since the connector reports itself as
disconnected.

I'm not sure what the reason was to drop the cap, but I think it would
be better to have a cap so WB connectors don't show up in, for ex,
xrandr

BR,
-R
_______________________________________________
Freedreno mailing list
Freedreno@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/freedreno

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [RFC 1/4] drm: Add writeback connector type
@ 2018-02-23 16:25           ` Rob Clark
  0 siblings, 0 replies; 41+ messages in thread
From: Rob Clark @ 2018-02-23 16:25 UTC (permalink / raw)
  To: Sean Paul
  Cc: dri-devel, freedreno, linux-arm-msm, Brian Starkey, Liviu Dudau,
	Mihail Atanassov, Gustavo Padovan, Maarten Lankhorst,
	David Airlie, Jonathan Corbet, linux-doc,
	Linux Kernel Mailing List

On Fri, Feb 23, 2018 at 10:59 AM, Sean Paul <seanpaul@chromium.org> wrote:
>
> Have we considered hiding writeback behind a client cap instead?

It is kinda *almost* unneeded, since the connector reports itself as
disconnected.

I'm not sure what the reason was to drop the cap, but I think it would
be better to have a cap so WB connectors don't show up in, for ex,
xrandr

BR,
-R

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [RFC 4/4] drm/msm/mdp5: writeback support
  2018-02-23 13:17     ` Rob Clark
@ 2018-02-23 16:30         ` Sean Paul
  -1 siblings, 0 replies; 41+ messages in thread
From: Sean Paul @ 2018-02-23 16:30 UTC (permalink / raw)
  To: Rob Clark
  Cc: Archit Taneja, Laurent Pinchart, Neil Armstrong, David Airlie,
	linux-arm-msm-u79uwXL29TY76Z2rM5mHXA, Liviu Dudau,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	Sushmita Susheelendra, Sean Paul, Daniel Vetter,
	freedreno-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, Brian Starkey,
	Ville Syrjälä

On Fri, Feb 23, 2018 at 08:17:54AM -0500, Rob Clark wrote:
> In a way, based on the original writeback patch from Jilai Wang, but a
> lot has shifted around since then.
> 
> Signed-off-by: Rob Clark <robdclark@gmail.com>
> ---
>  drivers/gpu/drm/msm/Makefile              |   1 +
>  drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c |  23 +-
>  drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c  |  38 +++-
>  drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.h  |   7 +-
>  drivers/gpu/drm/msm/disp/mdp5/mdp5_wb.c   | 367 ++++++++++++++++++++++++++++++
>  drivers/gpu/drm/msm/dsi/dsi_host.c        |   4 +-
>  6 files changed, 431 insertions(+), 9 deletions(-)
>  create mode 100644 drivers/gpu/drm/msm/disp/mdp5/mdp5_wb.c
> 
> diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
> index cd40c050b2d7..c9f50adef2db 100644
> --- a/drivers/gpu/drm/msm/Makefile
> +++ b/drivers/gpu/drm/msm/Makefile
> @@ -45,6 +45,7 @@ msm-y := \
>  	disp/mdp5/mdp5_mixer.o \
>  	disp/mdp5/mdp5_plane.o \
>  	disp/mdp5/mdp5_smp.o \
> +	disp/mdp5/mdp5_wb.o \
>  	msm_atomic.o \
>  	msm_debugfs.o \
>  	msm_drv.o \
> diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c
> index 9893e43ba6c5..b00ca88b741d 100644
> --- a/drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c
> +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c
> @@ -484,7 +484,11 @@ static void mdp5_crtc_atomic_enable(struct drm_crtc *crtc,
>  	}
>  
>  	/* Restore vblank irq handling after power is enabled */
> -	drm_crtc_vblank_on(crtc);
> +// TODO we can't ->get_scanout_pos() for wb (since virtual intf)..
> +// perhaps drm core should be clever enough not to drm_reset_vblank_timestamp()
> +// for virtual encoders / writeback?
> +	if (mdp5_cstate->pipeline.intf->type != INTF_WB)
> +		drm_crtc_vblank_on(crtc);
>  
>  	mdp5_crtc_mode_set_nofb(crtc);
>  
> @@ -518,7 +522,11 @@ int mdp5_crtc_setup_pipeline(struct drm_crtc *crtc,
>  		u32 caps;
>  		int ret;
>  
> -		caps = MDP_LM_CAP_DISPLAY;
> +		if (pipeline->intf->type == INTF_WB)
> +			caps = MDP_LM_CAP_WB;
> +		else
> +			caps = MDP_LM_CAP_DISPLAY;
> +
>  		if (need_right_mixer)
>  			caps |= MDP_LM_CAP_PAIR;
>  
> @@ -545,6 +553,7 @@ int mdp5_crtc_setup_pipeline(struct drm_crtc *crtc,
>  	mdp5_cstate->err_irqmask = intf2err(intf->num);
>  	mdp5_cstate->vblank_irqmask = intf2vblank(pipeline->mixer, intf);
>  
> +// XXX should we be treating WB as cmd_mode??
>  	if ((intf->type == INTF_DSI) &&
>  	    (intf->mode == MDP5_INTF_DSI_MODE_COMMAND)) {
>  		mdp5_cstate->pp_done_irqmask = lm2ppdone(pipeline->mixer);
> @@ -639,8 +648,12 @@ static int mdp5_crtc_atomic_check(struct drm_crtc *crtc,
>  	}
>  
>  	/* bail out early if there aren't any planes */
> -	if (!cnt)
> -		return 0;
> +	if (!cnt) {
> +		if (!state->active)
> +			return 0;
> +		dev_err(dev->dev, "%s has no planes!\n", crtc->name);
> +		return -EINVAL;
> +	}

This seems unrelated?

>  
>  	hw_cfg = mdp5_cfg_get_hw_config(mdp5_kms->cfg);
>  
> @@ -1160,7 +1173,7 @@ void mdp5_crtc_wait_for_commit_done(struct drm_crtc *crtc)
>  
>  	if (mdp5_cstate->cmd_mode)
>  		mdp5_crtc_wait_for_pp_done(crtc);
> -	else
> +	else if (mdp5_cstate->pipeline.intf->type != INTF_WB)
>  		mdp5_crtc_wait_for_flush_done(crtc);
>  }
>  
> diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c
> index 1f44d8f15ce1..239010905637 100644
> --- a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c
> +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c
> @@ -427,7 +427,8 @@ static int modeset_init(struct mdp5_kms *mdp5_kms)
>  	 * the MDP5 interfaces) than the number of layer mixers present in HW,
>  	 * but let's be safe here anyway
>  	 */
> -	num_crtcs = min(priv->num_encoders, mdp5_kms->num_hwmixers);
> +	num_crtcs = min(priv->num_encoders + hw_cfg->wb.count,
> +			mdp5_kms->num_hwmixers);
>  
>  	/*
>  	 * Construct planes equaling the number of hw pipes, and CRTCs for the
> @@ -482,6 +483,33 @@ static int modeset_init(struct mdp5_kms *mdp5_kms)
>  		encoder->possible_crtcs = (1 << priv->num_crtcs) - 1;
>  	}
>  
> +	/*
> +	 * Lastly, construct writeback connectors.
> +	 */
> +	for (i = 0; i < hw_cfg->wb.count; i++) {
> +		struct drm_writeback_connector *wb_conn;
> +		struct mdp5_ctl *ctl;
> +
> +		ctl = mdp5_ctlm_request(mdp5_kms->ctlm, -1);
> +		if (!ctl) {
> +			dev_err(dev->dev,
> +				"failed to allocate ctl for writeback %d\n", i);
> +			continue;
> +		}
> +
> +		wb_conn = mdp5_wb_connector_init(dev, ctl,
> +				hw_cfg->wb.instances[i].id);
> +		if (IS_ERR(wb_conn)) {
> +			ret = PTR_ERR(wb_conn);
> +			dev_err(dev->dev,
> +				"failed to construct writeback connector %d (%d)\n",
> +				i, ret);
> +			goto fail;
> +		}
> +
> +		wb_conn->encoder.possible_crtcs = (1 << priv->num_crtcs) - 1;
> +	}
> +
>  	return 0;
>  
>  fail:
> @@ -555,6 +583,10 @@ static bool mdp5_get_scanoutpos(struct drm_device *dev, unsigned int pipe,
>  		return false;
>  	}
>  
> +	/* unsupported for writeback: */
> +	if (encoder->encoder_type == DRM_MODE_ENCODER_VIRTUAL)
> +		return false;
> +
>  	vsw = mode->crtc_vsync_end - mode->crtc_vsync_start;
>  	vbp = mode->crtc_vtotal - mode->crtc_vsync_end;
>  
> @@ -610,6 +642,10 @@ static u32 mdp5_get_vblank_counter(struct drm_device *dev, unsigned int pipe)
>  	if (!encoder)
>  		return 0;
>  
> +	/* unsupported for writeback: */
> +	if (encoder->encoder_type == DRM_MODE_ENCODER_VIRTUAL)
> +		return 0;
> +
>  	return mdp5_encoder_get_framecount(encoder);
>  }
>  
> diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.h b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.h
> index 425a03d213e5..be0f93ef33e1 100644
> --- a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.h
> +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.h
> @@ -18,6 +18,8 @@
>  #ifndef __MDP5_KMS_H__
>  #define __MDP5_KMS_H__
>  
> +#include <drm/drm_writeback.h>
> +
>  #include "msm_drv.h"
>  #include "msm_kms.h"
>  #include "disp/mdp_kms.h"
> @@ -251,7 +253,7 @@ static inline uint32_t intf2vblank(struct mdp5_hw_mixer *mixer,
>  		return MDP5_IRQ_PING_PONG_0_RD_PTR << mixer->pp;
>  
>  	if (intf->type == INTF_WB)
> -		return MDP5_IRQ_WB_2_DONE;
> +		return MDP5_IRQ_WB_2_DONE | MDP5_IRQ_WB_0_DONE | MDP5_IRQ_WB_1_DONE;
>  
>  	switch (intf->num) {
>  	case 0:  return MDP5_IRQ_INTF0_VSYNC;
> @@ -330,4 +332,7 @@ static inline int mdp5_cmd_encoder_set_split_display(
>  }
>  #endif
>  
> +struct drm_writeback_connector *mdp5_wb_connector_init(struct drm_device *dev,
> +		struct mdp5_ctl *ctl, unsigned wb_id);
> +
>  #endif /* __MDP5_KMS_H__ */
> diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_wb.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_wb.c
> new file mode 100644
> index 000000000000..3dabd0a1aa8b
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_wb.c
> @@ -0,0 +1,367 @@
> +/*
> + * Copyright (C) 2018 Red Hat
> + * Author: Rob Clark <robdclark@gmail.com>
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms of the GNU General Public License version 2 as published by
> + * the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful, but WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
> + * more details.
> + *
> + * You should have received a copy of the GNU General Public License along with
> + * this program.  If not, see <http://www.gnu.org/licenses/>.

SPDX license

> + */
> +
> +#include "mdp5_kms.h"
> +
> +/*
> + * Writeback connector/encoder implementation:
> + */
> +
> +struct mdp5_wb_connector {
> +	struct drm_writeback_connector base;
> +
> +	u32 nformats;
> +	u32 formats[32];
> +
> +	unsigned id;
> +	struct mdp5_ctl *ctl;
> +	struct mdp5_interface *intf;
> +
> +	struct mdp_irq wb_done;
> +};
> +#define to_mdp5_wb_connector(x) container_of(x, struct mdp5_wb_connector, base)
> +
> +
> +static void mdp5_wb_connector_atomic_commit(struct drm_connector *connector,
> +		struct drm_writeback_job *job);
> +
> +static int mdp5_wb_connector_get_modes(struct drm_connector *connector)
> +{
> +	struct drm_device *dev = connector->dev;
> +
> +	return drm_add_modes_noedid(connector, dev->mode_config.max_width,
> +			dev->mode_config.max_height);

I guess the plan is to let userspace provide user-defined modes for wb? Is it
ever useful to have this token mode?

> +}
> +
> +static enum drm_mode_status
> +mdp5_wb_connector_mode_valid(struct drm_connector *connector,
> +		struct drm_display_mode *mode)
> +{
> +	struct drm_device *dev = connector->dev;
> +	struct drm_mode_config *mode_config = &dev->mode_config;
> +	int w = mode->hdisplay, h = mode->vdisplay;
> +
> +	if ((w < mode_config->min_width) || (w > mode_config->max_width))
> +		return MODE_BAD_HVALUE;
> +
> +	if ((h < mode_config->min_height) || (h > mode_config->max_height))
> +		return MODE_BAD_VVALUE;
> +
> +	return MODE_OK;
> +}

Might be useful for these to migrate into drm_writeback_connector.c as helpers.

> +
> +const struct drm_connector_helper_funcs mdp5_wb_connector_helper_funcs = {
> +	.get_modes = mdp5_wb_connector_get_modes,
> +	.mode_valid = mdp5_wb_connector_mode_valid,
> +	.atomic_commit = mdp5_wb_connector_atomic_commit,
> +};
> +
> +static enum drm_connector_status
> +mdp5_wb_connector_detect(struct drm_connector *connector, bool force)
> +{
> +	return connector_status_disconnected;
> +}

This should be a helper as well.

> +
> +static void mdp5_wb_connector_destroy(struct drm_connector *connector)
> +{
> +	drm_connector_cleanup(connector);
> +}

Just use drm_connector_cleanup directly below

> +
> +static const struct drm_connector_funcs mdp5_wb_connector_funcs = {
> +	.reset = drm_atomic_helper_connector_reset,
> +	.detect = mdp5_wb_connector_detect,
> +	.fill_modes = drm_helper_probe_single_connector_modes,
> +	.destroy = mdp5_wb_connector_destroy,
> +	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
> +	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> +};
> +
> +static int
> +mdp5_wb_encoder_atomic_check(struct drm_encoder *encoder,
> +		struct drm_crtc_state *crtc_state,
> +		struct drm_connector_state *conn_state)
> +{
> +	struct msm_drm_private *priv = encoder->dev->dev_private;
> +	struct mdp5_crtc_state *mdp5_cstate = to_mdp5_crtc_state(crtc_state);
> +	struct mdp5_wb_connector *mdp5_wb = to_mdp5_wb_connector(
> +		to_wb_connector(conn_state->connector));
> +	struct drm_framebuffer *fb;
> +	const struct msm_format *format;
> +	const struct mdp_format *mdp_fmt;
> +	struct drm_format_name_buf format_name;
> +	int ret;
> +
> +	if (!conn_state->writeback_job || !conn_state->writeback_job->fb)
> +		return 0;
> +
> +	fb = conn_state->writeback_job->fb;
> +
> +	DBG("wb[%u]: check writeback %ux%u@%s", mdp5_wb->id,
> +		fb->width, fb->height,
> +		drm_get_format_name(fb->format->format, &format_name));
> +
> +	format = mdp_get_format(priv->kms, fb->format->format);
> +	if (!format) {
> +		DBG("Invalid pixel format!");
> +		return -EINVAL;
> +	}
> +
> +	mdp_fmt = to_mdp_format(format);
> +	if (MDP_FORMAT_IS_YUV(mdp_fmt)) {
> +		switch (mdp_fmt->chroma_sample) {
> +		case CHROMA_420:
> +		case CHROMA_H2V1:
> +			/* supported */
> +			break;
> +		case CHROMA_H1V2:
> +		default:
> +			DBG("unsupported wb chroma samp=%d\n",
> +				mdp_fmt->chroma_sample);
> +			return -EINVAL;
> +		}
> +	}
> +
> +	/* TODO I think we would prefer to have proper prepare_fb()/cleanup_fb()
> +	 * vfuncs, as with plane..  Also, where to unprepare?
> +	 */
> +	ret = msm_framebuffer_prepare(fb, priv->kms->aspace);
> +	if (ret)
> +		return ret;
> +
> +	mdp5_cstate->ctl = mdp5_wb->ctl;
> +	mdp5_cstate->pipeline.intf = mdp5_wb->intf;
> +	mdp5_cstate->defer_start = true;
> +
> +	return 0;
> +}
> +
> +static void
> +wb_csc_setup(struct mdp5_kms *mdp5_kms, u32 wb_id, struct csc_cfg *csc)
> +{
> +	uint32_t  i;
> +	uint32_t *matrix;
> +
> +	if (unlikely(!csc))
> +		return;
> +
> +	matrix = csc->matrix;
> +	mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_0(wb_id),
> +		MDP5_WB_CSC_MATRIX_COEFF_0_COEFF_11(matrix[0]) |
> +		MDP5_WB_CSC_MATRIX_COEFF_0_COEFF_12(matrix[1]));
> +	mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_1(wb_id),
> +		MDP5_WB_CSC_MATRIX_COEFF_1_COEFF_13(matrix[2]) |
> +		MDP5_WB_CSC_MATRIX_COEFF_1_COEFF_21(matrix[3]));
> +	mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_2(wb_id),
> +		MDP5_WB_CSC_MATRIX_COEFF_2_COEFF_22(matrix[4]) |
> +		MDP5_WB_CSC_MATRIX_COEFF_2_COEFF_23(matrix[5]));
> +	mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_3(wb_id),
> +		MDP5_WB_CSC_MATRIX_COEFF_3_COEFF_31(matrix[6]) |
> +		MDP5_WB_CSC_MATRIX_COEFF_3_COEFF_32(matrix[7]));
> +	mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_4(wb_id),
> +		MDP5_WB_CSC_MATRIX_COEFF_4_COEFF_33(matrix[8]));
> +
> +	for (i = 0; i < ARRAY_SIZE(csc->pre_bias); i++) {
> +		uint32_t *pre_clamp = csc->pre_clamp;
> +		uint32_t *post_clamp = csc->post_clamp;
> +
> +		mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_COMP_PRECLAMP(wb_id, i),
> +			MDP5_WB_CSC_COMP_PRECLAMP_REG_HIGH(pre_clamp[2*i+1]) |
> +			MDP5_WB_CSC_COMP_PRECLAMP_REG_LOW(pre_clamp[2*i]));
> +
> +		mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_COMP_POSTCLAMP(wb_id, i),
> +			MDP5_WB_CSC_COMP_POSTCLAMP_REG_HIGH(post_clamp[2*i+1]) |
> +			MDP5_WB_CSC_COMP_POSTCLAMP_REG_LOW(post_clamp[2*i]));
> +
> +		mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_COMP_PREBIAS(wb_id, i),
> +			MDP5_WB_CSC_COMP_PREBIAS_REG_VALUE(csc->pre_bias[i]));
> +
> +		mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_COMP_POSTBIAS(wb_id, i),
> +			MDP5_WB_CSC_COMP_POSTBIAS_REG_VALUE(csc->post_bias[i]));
> +	}
> +}
> +
> +static void
> +mdp5_wb_connector_atomic_commit(struct drm_connector *connector,
> +		struct drm_writeback_job *job)
> +{
> +	struct msm_drm_private *priv = connector->dev->dev_private;
> +	struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(priv->kms));
> +	struct drm_connector_state *conn_state = connector->state;
> +	struct drm_writeback_connector *wb_conn = to_wb_connector(connector);
> +	struct mdp5_crtc_state *mdp5_crtc_state =
> +		to_mdp5_crtc_state(wb_conn->encoder.crtc->state);
> +	struct mdp5_wb_connector *mdp5_wb = to_mdp5_wb_connector(wb_conn);
> +	struct drm_framebuffer *fb = job->fb;
> +	struct drm_format_name_buf format_name;
> +	const struct mdp_format *fmt =
> +		to_mdp_format(mdp_get_format(priv->kms, fb->format->format));
> +	u32 ystride0, ystride1, outsize;
> +	u32 dst_format, pattern, opmode = 0;
> +
> +	DBG("wb[%u]: kick writeback %ux%u@%s", mdp5_wb->id,
> +		fb->width, fb->height,
> +		drm_get_format_name(fb->format->format, &format_name));
> +
> +	/* queue job before anything that can trigger completion irq */
> +	drm_writeback_queue_job(wb_conn, job);
> +	conn_state->writeback_job = NULL;
> +
> +	mdp_irq_register(&mdp5_kms->base, &mdp5_wb->wb_done);
> +
> +	if (MDP_FORMAT_IS_YUV(fmt)) {
> +		wb_csc_setup(mdp5_kms, mdp5_wb->id,
> +			mdp_get_default_csc_cfg(CSC_RGB2YUV));
> +
> +		opmode |= MDP5_WB_DST_OP_MODE_CSC_EN |
> +			MDP5_WB_DST_OP_MODE_CSC_SRC_DATA_FORMAT(DATA_FORMAT_RGB) |
> +			MDP5_WB_DST_OP_MODE_CSC_DST_DATA_FORMAT(DATA_FORMAT_YUV);
> +
> +		switch (fmt->chroma_sample) {
> +		case CHROMA_420:
> +		case CHROMA_H2V1:
> +			opmode |= MDP5_WB_DST_OP_MODE_CHROMA_DWN_SAMPLE_EN;
> +			break;
> +		case CHROMA_H1V2:
> +		default:
> +			WARN(1, "unsupported wb chroma samp=%d\n",
> +				fmt->chroma_sample);
> +			return;
> +		}
> +	}
> +
> +	dst_format = MDP5_WB_DST_FORMAT_DST_CHROMA_SAMP(fmt->chroma_sample) |
> +		MDP5_WB_DST_FORMAT_WRITE_PLANES(fmt->fetch_type) |
> +		MDP5_WB_DST_FORMAT_DSTC3_OUT(fmt->bpc_a) |
> +		MDP5_WB_DST_FORMAT_DSTC2_OUT(fmt->bpc_r) |
> +		MDP5_WB_DST_FORMAT_DSTC1_OUT(fmt->bpc_b) |
> +		MDP5_WB_DST_FORMAT_DSTC0_OUT(fmt->bpc_g) |
> +		COND(fmt->unpack_tight, MDP5_WB_DST_FORMAT_PACK_TIGHT) |
> +		MDP5_WB_DST_FORMAT_PACK_COUNT(fmt->unpack_count - 1) |
> +		MDP5_WB_DST_FORMAT_DST_BPP(fmt->cpp - 1);
> +
> +	if (fmt->bpc_a || fmt->alpha_enable) {
> +		dst_format |= MDP5_WB_DST_FORMAT_DSTC3_EN;
> +		if (!fmt->alpha_enable)
> +			dst_format |= MDP5_WB_DST_FORMAT_DST_ALPHA_X;
> +	}
> +
> +	pattern = MDP5_WB_DST_PACK_PATTERN_ELEMENT3(fmt->unpack[3]) |
> +		MDP5_WB_DST_PACK_PATTERN_ELEMENT2(fmt->unpack[2]) |
> +		MDP5_WB_DST_PACK_PATTERN_ELEMENT1(fmt->unpack[1]) |
> +		MDP5_WB_DST_PACK_PATTERN_ELEMENT0(fmt->unpack[0]);
> +
> +	ystride0 = MDP5_WB_DST_YSTRIDE0_DST0_YSTRIDE(fb->pitches[0]) |
> +		MDP5_WB_DST_YSTRIDE0_DST1_YSTRIDE(fb->pitches[1]);
> +	ystride1 = MDP5_WB_DST_YSTRIDE1_DST2_YSTRIDE(fb->pitches[2]) |
> +		MDP5_WB_DST_YSTRIDE1_DST3_YSTRIDE(fb->pitches[3]);
> +
> +	/* get the output resolution from WB device */
> +	outsize = MDP5_WB_OUT_SIZE_DST_H(fb->height) |
> +		MDP5_WB_OUT_SIZE_DST_W(fb->width);
> +
> +	mdp5_write(mdp5_kms, REG_MDP5_WB_ALPHA_X_VALUE(mdp5_wb->id), 0xff);
> +	mdp5_write(mdp5_kms, REG_MDP5_WB_DST_FORMAT(mdp5_wb->id), dst_format);
> +	mdp5_write(mdp5_kms, REG_MDP5_WB_DST_OP_MODE(mdp5_wb->id), opmode);
> +	mdp5_write(mdp5_kms, REG_MDP5_WB_DST_PACK_PATTERN(mdp5_wb->id), pattern);
> +	mdp5_write(mdp5_kms, REG_MDP5_WB_DST_YSTRIDE0(mdp5_wb->id), ystride0);
> +	mdp5_write(mdp5_kms, REG_MDP5_WB_DST_YSTRIDE1(mdp5_wb->id), ystride1);
> +	mdp5_write(mdp5_kms, REG_MDP5_WB_OUT_SIZE(mdp5_wb->id), outsize);
> +
> +	mdp5_crtc_set_pipeline(wb_conn->encoder.crtc);
> +
> +	mdp5_write(mdp5_kms, REG_MDP5_WB_DST0_ADDR(mdp5_wb->id),
> +		msm_framebuffer_iova(fb, priv->kms->aspace, 0));
> +	mdp5_write(mdp5_kms, REG_MDP5_WB_DST1_ADDR(mdp5_wb->id),
> +		msm_framebuffer_iova(fb, priv->kms->aspace, 1));
> +	mdp5_write(mdp5_kms, REG_MDP5_WB_DST2_ADDR(mdp5_wb->id),
> +		msm_framebuffer_iova(fb, priv->kms->aspace, 2));
> +	mdp5_write(mdp5_kms, REG_MDP5_WB_DST3_ADDR(mdp5_wb->id),
> +		msm_framebuffer_iova(fb, priv->kms->aspace, 3));
> +
> +	/* Notify ctl that wb buffer is ready to trigger start */
> +	mdp5_ctl_commit(mdp5_wb->ctl, &mdp5_crtc_state->pipeline,
> +		MDP5_CTL_FLUSH_WB, true);
> +
> +	mdp5_ctl_set_encoder_state(mdp5_wb->ctl,
> +		&mdp5_crtc_state->pipeline, true);
> +}
> +
> +static void mdp5_wb_done_irq(struct mdp_irq *irq, uint32_t irqstatus)
> +{
> +	struct mdp5_wb_connector *mdp5_wb =
> +		container_of(irq, struct mdp5_wb_connector, wb_done);
> +	struct mdp5_crtc_state *mdp5_crtc_state =
> +		to_mdp5_crtc_state(mdp5_wb->base.encoder.crtc->state);
> +	struct msm_drm_private *priv = mdp5_wb->base.base.dev->dev_private;
> +
> +	mdp_irq_unregister(to_mdp_kms(priv->kms), &mdp5_wb->wb_done);
> +
> +	mdp5_ctl_set_encoder_state(mdp5_wb->ctl,
> +		&mdp5_crtc_state->pipeline, false);
> +
> +	drm_writeback_signal_completion(&mdp5_wb->base, 0);
> +}
> +
> +static const struct drm_encoder_helper_funcs mdp5_wb_encoder_helper_funcs = {
> +	.atomic_check = mdp5_wb_encoder_atomic_check,
> +};
> +
> +struct drm_writeback_connector *
> +mdp5_wb_connector_init(struct drm_device *dev, struct mdp5_ctl *ctl,
> +		unsigned wb_id)
> +{
> +	struct drm_connector *connector = NULL;
> +	struct mdp5_wb_connector *mdp5_wb;
> +
> +	mdp5_wb = kzalloc(sizeof(*mdp5_wb), GFP_KERNEL);
> +	if (!mdp5_wb)
> +		return ERR_PTR(-ENOMEM);
> +
> +	mdp5_wb->id = wb_id;
> +	mdp5_wb->ctl = ctl;
> +
> +	/* construct a dummy intf for WB: */
> +// TODO un-inline this (and also in interface_init())
> +	mdp5_wb->intf = kzalloc(sizeof(*mdp5_wb->intf), GFP_KERNEL);
> +	mdp5_wb->intf->num = -1;
> +	mdp5_wb->intf->type = INTF_WB;
> +	mdp5_wb->intf->mode = MDP5_INTF_WB_MODE_LINE;
> +	mdp5_wb->intf->idx = -1;
> +
> +	mdp5_wb->wb_done.irq = mdp5_wb_done_irq;
> +// TODO just register for all wb irq's until I figure out the mapping..
> +	mdp5_wb->wb_done.irqmask = MDP5_IRQ_WB_0_DONE | MDP5_IRQ_WB_1_DONE | MDP5_IRQ_WB_2_DONE;
> +
> +	connector = &mdp5_wb->base.base;
> +
> +	drm_connector_helper_add(connector, &mdp5_wb_connector_helper_funcs);
> +
> +	mdp5_wb->nformats = mdp_get_formats(mdp5_wb->formats,
> +		ARRAY_SIZE(mdp5_wb->formats), false);
> +
> +	drm_writeback_connector_init(dev,
> +		&mdp5_wb->base,
> +		&mdp5_wb_connector_funcs,
> +		&mdp5_wb_encoder_helper_funcs,
> +		mdp5_wb->formats,
> +		mdp5_wb->nformats);
> +
> +	connector->interlace_allowed = 0;
> +	connector->doublescan_allowed = 0;

These are handled by drm_writeback_connector_init (and kzalloc)

> +
> +	return &mdp5_wb->base;
> +}
> diff --git a/drivers/gpu/drm/msm/dsi/dsi_host.c b/drivers/gpu/drm/msm/dsi/dsi_host.c
> index 7a03a9489708..422f524f7562 100644
> --- a/drivers/gpu/drm/msm/dsi/dsi_host.c
> +++ b/drivers/gpu/drm/msm/dsi/dsi_host.c
> @@ -741,7 +741,7 @@ static void dsi_intr_ctrl(struct msm_dsi_host *msm_host, u32 mask, int enable)
>  	else
>  		intr &= ~mask;
>  
> -	DBG("intr=%x enable=%d", intr, enable);
> +	VERB("intr=%x enable=%d", intr, enable);
>  
>  	dsi_write(msm_host, REG_DSI_INTR_CTRL, intr);
>  	spin_unlock_irqrestore(&msm_host->intr_lock, flags);
> @@ -1465,7 +1465,7 @@ static irqreturn_t dsi_host_irq(int irq, void *ptr)
>  	dsi_write(msm_host, REG_DSI_INTR_CTRL, isr);
>  	spin_unlock_irqrestore(&msm_host->intr_lock, flags);
>  
> -	DBG("isr=0x%x, id=%d", isr, msm_host->id);
> +	VERB("isr=0x%x, id=%d", isr, msm_host->id);
>  
>  	if (isr & DSI_IRQ_ERROR)
>  		dsi_error(msm_host);
> -- 
> 2.14.3
> 

-- 
Sean Paul, Software Engineer, Google / Chromium OS
_______________________________________________
Freedreno mailing list
Freedreno@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/freedreno

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [RFC 4/4] drm/msm/mdp5: writeback support
@ 2018-02-23 16:30         ` Sean Paul
  0 siblings, 0 replies; 41+ messages in thread
From: Sean Paul @ 2018-02-23 16:30 UTC (permalink / raw)
  To: Rob Clark
  Cc: dri-devel, freedreno, linux-arm-msm, Brian Starkey, Liviu Dudau,
	David Airlie, Archit Taneja, Daniel Vetter, Laurent Pinchart,
	Ville Syrjälä,
	Neil Armstrong, Sean Paul, Sushmita Susheelendra, linux-kernel

On Fri, Feb 23, 2018 at 08:17:54AM -0500, Rob Clark wrote:
> In a way, based on the original writeback patch from Jilai Wang, but a
> lot has shifted around since then.
> 
> Signed-off-by: Rob Clark <robdclark@gmail.com>
> ---
>  drivers/gpu/drm/msm/Makefile              |   1 +
>  drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c |  23 +-
>  drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c  |  38 +++-
>  drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.h  |   7 +-
>  drivers/gpu/drm/msm/disp/mdp5/mdp5_wb.c   | 367 ++++++++++++++++++++++++++++++
>  drivers/gpu/drm/msm/dsi/dsi_host.c        |   4 +-
>  6 files changed, 431 insertions(+), 9 deletions(-)
>  create mode 100644 drivers/gpu/drm/msm/disp/mdp5/mdp5_wb.c
> 
> diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
> index cd40c050b2d7..c9f50adef2db 100644
> --- a/drivers/gpu/drm/msm/Makefile
> +++ b/drivers/gpu/drm/msm/Makefile
> @@ -45,6 +45,7 @@ msm-y := \
>  	disp/mdp5/mdp5_mixer.o \
>  	disp/mdp5/mdp5_plane.o \
>  	disp/mdp5/mdp5_smp.o \
> +	disp/mdp5/mdp5_wb.o \
>  	msm_atomic.o \
>  	msm_debugfs.o \
>  	msm_drv.o \
> diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c
> index 9893e43ba6c5..b00ca88b741d 100644
> --- a/drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c
> +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c
> @@ -484,7 +484,11 @@ static void mdp5_crtc_atomic_enable(struct drm_crtc *crtc,
>  	}
>  
>  	/* Restore vblank irq handling after power is enabled */
> -	drm_crtc_vblank_on(crtc);
> +// TODO we can't ->get_scanout_pos() for wb (since virtual intf)..
> +// perhaps drm core should be clever enough not to drm_reset_vblank_timestamp()
> +// for virtual encoders / writeback?
> +	if (mdp5_cstate->pipeline.intf->type != INTF_WB)
> +		drm_crtc_vblank_on(crtc);
>  
>  	mdp5_crtc_mode_set_nofb(crtc);
>  
> @@ -518,7 +522,11 @@ int mdp5_crtc_setup_pipeline(struct drm_crtc *crtc,
>  		u32 caps;
>  		int ret;
>  
> -		caps = MDP_LM_CAP_DISPLAY;
> +		if (pipeline->intf->type == INTF_WB)
> +			caps = MDP_LM_CAP_WB;
> +		else
> +			caps = MDP_LM_CAP_DISPLAY;
> +
>  		if (need_right_mixer)
>  			caps |= MDP_LM_CAP_PAIR;
>  
> @@ -545,6 +553,7 @@ int mdp5_crtc_setup_pipeline(struct drm_crtc *crtc,
>  	mdp5_cstate->err_irqmask = intf2err(intf->num);
>  	mdp5_cstate->vblank_irqmask = intf2vblank(pipeline->mixer, intf);
>  
> +// XXX should we be treating WB as cmd_mode??
>  	if ((intf->type == INTF_DSI) &&
>  	    (intf->mode == MDP5_INTF_DSI_MODE_COMMAND)) {
>  		mdp5_cstate->pp_done_irqmask = lm2ppdone(pipeline->mixer);
> @@ -639,8 +648,12 @@ static int mdp5_crtc_atomic_check(struct drm_crtc *crtc,
>  	}
>  
>  	/* bail out early if there aren't any planes */
> -	if (!cnt)
> -		return 0;
> +	if (!cnt) {
> +		if (!state->active)
> +			return 0;
> +		dev_err(dev->dev, "%s has no planes!\n", crtc->name);
> +		return -EINVAL;
> +	}

This seems unrelated?

>  
>  	hw_cfg = mdp5_cfg_get_hw_config(mdp5_kms->cfg);
>  
> @@ -1160,7 +1173,7 @@ void mdp5_crtc_wait_for_commit_done(struct drm_crtc *crtc)
>  
>  	if (mdp5_cstate->cmd_mode)
>  		mdp5_crtc_wait_for_pp_done(crtc);
> -	else
> +	else if (mdp5_cstate->pipeline.intf->type != INTF_WB)
>  		mdp5_crtc_wait_for_flush_done(crtc);
>  }
>  
> diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c
> index 1f44d8f15ce1..239010905637 100644
> --- a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c
> +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c
> @@ -427,7 +427,8 @@ static int modeset_init(struct mdp5_kms *mdp5_kms)
>  	 * the MDP5 interfaces) than the number of layer mixers present in HW,
>  	 * but let's be safe here anyway
>  	 */
> -	num_crtcs = min(priv->num_encoders, mdp5_kms->num_hwmixers);
> +	num_crtcs = min(priv->num_encoders + hw_cfg->wb.count,
> +			mdp5_kms->num_hwmixers);
>  
>  	/*
>  	 * Construct planes equaling the number of hw pipes, and CRTCs for the
> @@ -482,6 +483,33 @@ static int modeset_init(struct mdp5_kms *mdp5_kms)
>  		encoder->possible_crtcs = (1 << priv->num_crtcs) - 1;
>  	}
>  
> +	/*
> +	 * Lastly, construct writeback connectors.
> +	 */
> +	for (i = 0; i < hw_cfg->wb.count; i++) {
> +		struct drm_writeback_connector *wb_conn;
> +		struct mdp5_ctl *ctl;
> +
> +		ctl = mdp5_ctlm_request(mdp5_kms->ctlm, -1);
> +		if (!ctl) {
> +			dev_err(dev->dev,
> +				"failed to allocate ctl for writeback %d\n", i);
> +			continue;
> +		}
> +
> +		wb_conn = mdp5_wb_connector_init(dev, ctl,
> +				hw_cfg->wb.instances[i].id);
> +		if (IS_ERR(wb_conn)) {
> +			ret = PTR_ERR(wb_conn);
> +			dev_err(dev->dev,
> +				"failed to construct writeback connector %d (%d)\n",
> +				i, ret);
> +			goto fail;
> +		}
> +
> +		wb_conn->encoder.possible_crtcs = (1 << priv->num_crtcs) - 1;
> +	}
> +
>  	return 0;
>  
>  fail:
> @@ -555,6 +583,10 @@ static bool mdp5_get_scanoutpos(struct drm_device *dev, unsigned int pipe,
>  		return false;
>  	}
>  
> +	/* unsupported for writeback: */
> +	if (encoder->encoder_type == DRM_MODE_ENCODER_VIRTUAL)
> +		return false;
> +
>  	vsw = mode->crtc_vsync_end - mode->crtc_vsync_start;
>  	vbp = mode->crtc_vtotal - mode->crtc_vsync_end;
>  
> @@ -610,6 +642,10 @@ static u32 mdp5_get_vblank_counter(struct drm_device *dev, unsigned int pipe)
>  	if (!encoder)
>  		return 0;
>  
> +	/* unsupported for writeback: */
> +	if (encoder->encoder_type == DRM_MODE_ENCODER_VIRTUAL)
> +		return 0;
> +
>  	return mdp5_encoder_get_framecount(encoder);
>  }
>  
> diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.h b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.h
> index 425a03d213e5..be0f93ef33e1 100644
> --- a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.h
> +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.h
> @@ -18,6 +18,8 @@
>  #ifndef __MDP5_KMS_H__
>  #define __MDP5_KMS_H__
>  
> +#include <drm/drm_writeback.h>
> +
>  #include "msm_drv.h"
>  #include "msm_kms.h"
>  #include "disp/mdp_kms.h"
> @@ -251,7 +253,7 @@ static inline uint32_t intf2vblank(struct mdp5_hw_mixer *mixer,
>  		return MDP5_IRQ_PING_PONG_0_RD_PTR << mixer->pp;
>  
>  	if (intf->type == INTF_WB)
> -		return MDP5_IRQ_WB_2_DONE;
> +		return MDP5_IRQ_WB_2_DONE | MDP5_IRQ_WB_0_DONE | MDP5_IRQ_WB_1_DONE;
>  
>  	switch (intf->num) {
>  	case 0:  return MDP5_IRQ_INTF0_VSYNC;
> @@ -330,4 +332,7 @@ static inline int mdp5_cmd_encoder_set_split_display(
>  }
>  #endif
>  
> +struct drm_writeback_connector *mdp5_wb_connector_init(struct drm_device *dev,
> +		struct mdp5_ctl *ctl, unsigned wb_id);
> +
>  #endif /* __MDP5_KMS_H__ */
> diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_wb.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_wb.c
> new file mode 100644
> index 000000000000..3dabd0a1aa8b
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_wb.c
> @@ -0,0 +1,367 @@
> +/*
> + * Copyright (C) 2018 Red Hat
> + * Author: Rob Clark <robdclark@gmail.com>
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms of the GNU General Public License version 2 as published by
> + * the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful, but WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
> + * more details.
> + *
> + * You should have received a copy of the GNU General Public License along with
> + * this program.  If not, see <http://www.gnu.org/licenses/>.

SPDX license

> + */
> +
> +#include "mdp5_kms.h"
> +
> +/*
> + * Writeback connector/encoder implementation:
> + */
> +
> +struct mdp5_wb_connector {
> +	struct drm_writeback_connector base;
> +
> +	u32 nformats;
> +	u32 formats[32];
> +
> +	unsigned id;
> +	struct mdp5_ctl *ctl;
> +	struct mdp5_interface *intf;
> +
> +	struct mdp_irq wb_done;
> +};
> +#define to_mdp5_wb_connector(x) container_of(x, struct mdp5_wb_connector, base)
> +
> +
> +static void mdp5_wb_connector_atomic_commit(struct drm_connector *connector,
> +		struct drm_writeback_job *job);
> +
> +static int mdp5_wb_connector_get_modes(struct drm_connector *connector)
> +{
> +	struct drm_device *dev = connector->dev;
> +
> +	return drm_add_modes_noedid(connector, dev->mode_config.max_width,
> +			dev->mode_config.max_height);

I guess the plan is to let userspace provide user-defined modes for wb? Is it
ever useful to have this token mode?

> +}
> +
> +static enum drm_mode_status
> +mdp5_wb_connector_mode_valid(struct drm_connector *connector,
> +		struct drm_display_mode *mode)
> +{
> +	struct drm_device *dev = connector->dev;
> +	struct drm_mode_config *mode_config = &dev->mode_config;
> +	int w = mode->hdisplay, h = mode->vdisplay;
> +
> +	if ((w < mode_config->min_width) || (w > mode_config->max_width))
> +		return MODE_BAD_HVALUE;
> +
> +	if ((h < mode_config->min_height) || (h > mode_config->max_height))
> +		return MODE_BAD_VVALUE;
> +
> +	return MODE_OK;
> +}

Might be useful for these to migrate into drm_writeback_connector.c as helpers.

> +
> +const struct drm_connector_helper_funcs mdp5_wb_connector_helper_funcs = {
> +	.get_modes = mdp5_wb_connector_get_modes,
> +	.mode_valid = mdp5_wb_connector_mode_valid,
> +	.atomic_commit = mdp5_wb_connector_atomic_commit,
> +};
> +
> +static enum drm_connector_status
> +mdp5_wb_connector_detect(struct drm_connector *connector, bool force)
> +{
> +	return connector_status_disconnected;
> +}

This should be a helper as well.

> +
> +static void mdp5_wb_connector_destroy(struct drm_connector *connector)
> +{
> +	drm_connector_cleanup(connector);
> +}

Just use drm_connector_cleanup directly below

> +
> +static const struct drm_connector_funcs mdp5_wb_connector_funcs = {
> +	.reset = drm_atomic_helper_connector_reset,
> +	.detect = mdp5_wb_connector_detect,
> +	.fill_modes = drm_helper_probe_single_connector_modes,
> +	.destroy = mdp5_wb_connector_destroy,
> +	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
> +	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> +};
> +
> +static int
> +mdp5_wb_encoder_atomic_check(struct drm_encoder *encoder,
> +		struct drm_crtc_state *crtc_state,
> +		struct drm_connector_state *conn_state)
> +{
> +	struct msm_drm_private *priv = encoder->dev->dev_private;
> +	struct mdp5_crtc_state *mdp5_cstate = to_mdp5_crtc_state(crtc_state);
> +	struct mdp5_wb_connector *mdp5_wb = to_mdp5_wb_connector(
> +		to_wb_connector(conn_state->connector));
> +	struct drm_framebuffer *fb;
> +	const struct msm_format *format;
> +	const struct mdp_format *mdp_fmt;
> +	struct drm_format_name_buf format_name;
> +	int ret;
> +
> +	if (!conn_state->writeback_job || !conn_state->writeback_job->fb)
> +		return 0;
> +
> +	fb = conn_state->writeback_job->fb;
> +
> +	DBG("wb[%u]: check writeback %ux%u@%s", mdp5_wb->id,
> +		fb->width, fb->height,
> +		drm_get_format_name(fb->format->format, &format_name));
> +
> +	format = mdp_get_format(priv->kms, fb->format->format);
> +	if (!format) {
> +		DBG("Invalid pixel format!");
> +		return -EINVAL;
> +	}
> +
> +	mdp_fmt = to_mdp_format(format);
> +	if (MDP_FORMAT_IS_YUV(mdp_fmt)) {
> +		switch (mdp_fmt->chroma_sample) {
> +		case CHROMA_420:
> +		case CHROMA_H2V1:
> +			/* supported */
> +			break;
> +		case CHROMA_H1V2:
> +		default:
> +			DBG("unsupported wb chroma samp=%d\n",
> +				mdp_fmt->chroma_sample);
> +			return -EINVAL;
> +		}
> +	}
> +
> +	/* TODO I think we would prefer to have proper prepare_fb()/cleanup_fb()
> +	 * vfuncs, as with plane..  Also, where to unprepare?
> +	 */
> +	ret = msm_framebuffer_prepare(fb, priv->kms->aspace);
> +	if (ret)
> +		return ret;
> +
> +	mdp5_cstate->ctl = mdp5_wb->ctl;
> +	mdp5_cstate->pipeline.intf = mdp5_wb->intf;
> +	mdp5_cstate->defer_start = true;
> +
> +	return 0;
> +}
> +
> +static void
> +wb_csc_setup(struct mdp5_kms *mdp5_kms, u32 wb_id, struct csc_cfg *csc)
> +{
> +	uint32_t  i;
> +	uint32_t *matrix;
> +
> +	if (unlikely(!csc))
> +		return;
> +
> +	matrix = csc->matrix;
> +	mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_0(wb_id),
> +		MDP5_WB_CSC_MATRIX_COEFF_0_COEFF_11(matrix[0]) |
> +		MDP5_WB_CSC_MATRIX_COEFF_0_COEFF_12(matrix[1]));
> +	mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_1(wb_id),
> +		MDP5_WB_CSC_MATRIX_COEFF_1_COEFF_13(matrix[2]) |
> +		MDP5_WB_CSC_MATRIX_COEFF_1_COEFF_21(matrix[3]));
> +	mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_2(wb_id),
> +		MDP5_WB_CSC_MATRIX_COEFF_2_COEFF_22(matrix[4]) |
> +		MDP5_WB_CSC_MATRIX_COEFF_2_COEFF_23(matrix[5]));
> +	mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_3(wb_id),
> +		MDP5_WB_CSC_MATRIX_COEFF_3_COEFF_31(matrix[6]) |
> +		MDP5_WB_CSC_MATRIX_COEFF_3_COEFF_32(matrix[7]));
> +	mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_4(wb_id),
> +		MDP5_WB_CSC_MATRIX_COEFF_4_COEFF_33(matrix[8]));
> +
> +	for (i = 0; i < ARRAY_SIZE(csc->pre_bias); i++) {
> +		uint32_t *pre_clamp = csc->pre_clamp;
> +		uint32_t *post_clamp = csc->post_clamp;
> +
> +		mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_COMP_PRECLAMP(wb_id, i),
> +			MDP5_WB_CSC_COMP_PRECLAMP_REG_HIGH(pre_clamp[2*i+1]) |
> +			MDP5_WB_CSC_COMP_PRECLAMP_REG_LOW(pre_clamp[2*i]));
> +
> +		mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_COMP_POSTCLAMP(wb_id, i),
> +			MDP5_WB_CSC_COMP_POSTCLAMP_REG_HIGH(post_clamp[2*i+1]) |
> +			MDP5_WB_CSC_COMP_POSTCLAMP_REG_LOW(post_clamp[2*i]));
> +
> +		mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_COMP_PREBIAS(wb_id, i),
> +			MDP5_WB_CSC_COMP_PREBIAS_REG_VALUE(csc->pre_bias[i]));
> +
> +		mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_COMP_POSTBIAS(wb_id, i),
> +			MDP5_WB_CSC_COMP_POSTBIAS_REG_VALUE(csc->post_bias[i]));
> +	}
> +}
> +
> +static void
> +mdp5_wb_connector_atomic_commit(struct drm_connector *connector,
> +		struct drm_writeback_job *job)
> +{
> +	struct msm_drm_private *priv = connector->dev->dev_private;
> +	struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(priv->kms));
> +	struct drm_connector_state *conn_state = connector->state;
> +	struct drm_writeback_connector *wb_conn = to_wb_connector(connector);
> +	struct mdp5_crtc_state *mdp5_crtc_state =
> +		to_mdp5_crtc_state(wb_conn->encoder.crtc->state);
> +	struct mdp5_wb_connector *mdp5_wb = to_mdp5_wb_connector(wb_conn);
> +	struct drm_framebuffer *fb = job->fb;
> +	struct drm_format_name_buf format_name;
> +	const struct mdp_format *fmt =
> +		to_mdp_format(mdp_get_format(priv->kms, fb->format->format));
> +	u32 ystride0, ystride1, outsize;
> +	u32 dst_format, pattern, opmode = 0;
> +
> +	DBG("wb[%u]: kick writeback %ux%u@%s", mdp5_wb->id,
> +		fb->width, fb->height,
> +		drm_get_format_name(fb->format->format, &format_name));
> +
> +	/* queue job before anything that can trigger completion irq */
> +	drm_writeback_queue_job(wb_conn, job);
> +	conn_state->writeback_job = NULL;
> +
> +	mdp_irq_register(&mdp5_kms->base, &mdp5_wb->wb_done);
> +
> +	if (MDP_FORMAT_IS_YUV(fmt)) {
> +		wb_csc_setup(mdp5_kms, mdp5_wb->id,
> +			mdp_get_default_csc_cfg(CSC_RGB2YUV));
> +
> +		opmode |= MDP5_WB_DST_OP_MODE_CSC_EN |
> +			MDP5_WB_DST_OP_MODE_CSC_SRC_DATA_FORMAT(DATA_FORMAT_RGB) |
> +			MDP5_WB_DST_OP_MODE_CSC_DST_DATA_FORMAT(DATA_FORMAT_YUV);
> +
> +		switch (fmt->chroma_sample) {
> +		case CHROMA_420:
> +		case CHROMA_H2V1:
> +			opmode |= MDP5_WB_DST_OP_MODE_CHROMA_DWN_SAMPLE_EN;
> +			break;
> +		case CHROMA_H1V2:
> +		default:
> +			WARN(1, "unsupported wb chroma samp=%d\n",
> +				fmt->chroma_sample);
> +			return;
> +		}
> +	}
> +
> +	dst_format = MDP5_WB_DST_FORMAT_DST_CHROMA_SAMP(fmt->chroma_sample) |
> +		MDP5_WB_DST_FORMAT_WRITE_PLANES(fmt->fetch_type) |
> +		MDP5_WB_DST_FORMAT_DSTC3_OUT(fmt->bpc_a) |
> +		MDP5_WB_DST_FORMAT_DSTC2_OUT(fmt->bpc_r) |
> +		MDP5_WB_DST_FORMAT_DSTC1_OUT(fmt->bpc_b) |
> +		MDP5_WB_DST_FORMAT_DSTC0_OUT(fmt->bpc_g) |
> +		COND(fmt->unpack_tight, MDP5_WB_DST_FORMAT_PACK_TIGHT) |
> +		MDP5_WB_DST_FORMAT_PACK_COUNT(fmt->unpack_count - 1) |
> +		MDP5_WB_DST_FORMAT_DST_BPP(fmt->cpp - 1);
> +
> +	if (fmt->bpc_a || fmt->alpha_enable) {
> +		dst_format |= MDP5_WB_DST_FORMAT_DSTC3_EN;
> +		if (!fmt->alpha_enable)
> +			dst_format |= MDP5_WB_DST_FORMAT_DST_ALPHA_X;
> +	}
> +
> +	pattern = MDP5_WB_DST_PACK_PATTERN_ELEMENT3(fmt->unpack[3]) |
> +		MDP5_WB_DST_PACK_PATTERN_ELEMENT2(fmt->unpack[2]) |
> +		MDP5_WB_DST_PACK_PATTERN_ELEMENT1(fmt->unpack[1]) |
> +		MDP5_WB_DST_PACK_PATTERN_ELEMENT0(fmt->unpack[0]);
> +
> +	ystride0 = MDP5_WB_DST_YSTRIDE0_DST0_YSTRIDE(fb->pitches[0]) |
> +		MDP5_WB_DST_YSTRIDE0_DST1_YSTRIDE(fb->pitches[1]);
> +	ystride1 = MDP5_WB_DST_YSTRIDE1_DST2_YSTRIDE(fb->pitches[2]) |
> +		MDP5_WB_DST_YSTRIDE1_DST3_YSTRIDE(fb->pitches[3]);
> +
> +	/* get the output resolution from WB device */
> +	outsize = MDP5_WB_OUT_SIZE_DST_H(fb->height) |
> +		MDP5_WB_OUT_SIZE_DST_W(fb->width);
> +
> +	mdp5_write(mdp5_kms, REG_MDP5_WB_ALPHA_X_VALUE(mdp5_wb->id), 0xff);
> +	mdp5_write(mdp5_kms, REG_MDP5_WB_DST_FORMAT(mdp5_wb->id), dst_format);
> +	mdp5_write(mdp5_kms, REG_MDP5_WB_DST_OP_MODE(mdp5_wb->id), opmode);
> +	mdp5_write(mdp5_kms, REG_MDP5_WB_DST_PACK_PATTERN(mdp5_wb->id), pattern);
> +	mdp5_write(mdp5_kms, REG_MDP5_WB_DST_YSTRIDE0(mdp5_wb->id), ystride0);
> +	mdp5_write(mdp5_kms, REG_MDP5_WB_DST_YSTRIDE1(mdp5_wb->id), ystride1);
> +	mdp5_write(mdp5_kms, REG_MDP5_WB_OUT_SIZE(mdp5_wb->id), outsize);
> +
> +	mdp5_crtc_set_pipeline(wb_conn->encoder.crtc);
> +
> +	mdp5_write(mdp5_kms, REG_MDP5_WB_DST0_ADDR(mdp5_wb->id),
> +		msm_framebuffer_iova(fb, priv->kms->aspace, 0));
> +	mdp5_write(mdp5_kms, REG_MDP5_WB_DST1_ADDR(mdp5_wb->id),
> +		msm_framebuffer_iova(fb, priv->kms->aspace, 1));
> +	mdp5_write(mdp5_kms, REG_MDP5_WB_DST2_ADDR(mdp5_wb->id),
> +		msm_framebuffer_iova(fb, priv->kms->aspace, 2));
> +	mdp5_write(mdp5_kms, REG_MDP5_WB_DST3_ADDR(mdp5_wb->id),
> +		msm_framebuffer_iova(fb, priv->kms->aspace, 3));
> +
> +	/* Notify ctl that wb buffer is ready to trigger start */
> +	mdp5_ctl_commit(mdp5_wb->ctl, &mdp5_crtc_state->pipeline,
> +		MDP5_CTL_FLUSH_WB, true);
> +
> +	mdp5_ctl_set_encoder_state(mdp5_wb->ctl,
> +		&mdp5_crtc_state->pipeline, true);
> +}
> +
> +static void mdp5_wb_done_irq(struct mdp_irq *irq, uint32_t irqstatus)
> +{
> +	struct mdp5_wb_connector *mdp5_wb =
> +		container_of(irq, struct mdp5_wb_connector, wb_done);
> +	struct mdp5_crtc_state *mdp5_crtc_state =
> +		to_mdp5_crtc_state(mdp5_wb->base.encoder.crtc->state);
> +	struct msm_drm_private *priv = mdp5_wb->base.base.dev->dev_private;
> +
> +	mdp_irq_unregister(to_mdp_kms(priv->kms), &mdp5_wb->wb_done);
> +
> +	mdp5_ctl_set_encoder_state(mdp5_wb->ctl,
> +		&mdp5_crtc_state->pipeline, false);
> +
> +	drm_writeback_signal_completion(&mdp5_wb->base, 0);
> +}
> +
> +static const struct drm_encoder_helper_funcs mdp5_wb_encoder_helper_funcs = {
> +	.atomic_check = mdp5_wb_encoder_atomic_check,
> +};
> +
> +struct drm_writeback_connector *
> +mdp5_wb_connector_init(struct drm_device *dev, struct mdp5_ctl *ctl,
> +		unsigned wb_id)
> +{
> +	struct drm_connector *connector = NULL;
> +	struct mdp5_wb_connector *mdp5_wb;
> +
> +	mdp5_wb = kzalloc(sizeof(*mdp5_wb), GFP_KERNEL);
> +	if (!mdp5_wb)
> +		return ERR_PTR(-ENOMEM);
> +
> +	mdp5_wb->id = wb_id;
> +	mdp5_wb->ctl = ctl;
> +
> +	/* construct a dummy intf for WB: */
> +// TODO un-inline this (and also in interface_init())
> +	mdp5_wb->intf = kzalloc(sizeof(*mdp5_wb->intf), GFP_KERNEL);
> +	mdp5_wb->intf->num = -1;
> +	mdp5_wb->intf->type = INTF_WB;
> +	mdp5_wb->intf->mode = MDP5_INTF_WB_MODE_LINE;
> +	mdp5_wb->intf->idx = -1;
> +
> +	mdp5_wb->wb_done.irq = mdp5_wb_done_irq;
> +// TODO just register for all wb irq's until I figure out the mapping..
> +	mdp5_wb->wb_done.irqmask = MDP5_IRQ_WB_0_DONE | MDP5_IRQ_WB_1_DONE | MDP5_IRQ_WB_2_DONE;
> +
> +	connector = &mdp5_wb->base.base;
> +
> +	drm_connector_helper_add(connector, &mdp5_wb_connector_helper_funcs);
> +
> +	mdp5_wb->nformats = mdp_get_formats(mdp5_wb->formats,
> +		ARRAY_SIZE(mdp5_wb->formats), false);
> +
> +	drm_writeback_connector_init(dev,
> +		&mdp5_wb->base,
> +		&mdp5_wb_connector_funcs,
> +		&mdp5_wb_encoder_helper_funcs,
> +		mdp5_wb->formats,
> +		mdp5_wb->nformats);
> +
> +	connector->interlace_allowed = 0;
> +	connector->doublescan_allowed = 0;

These are handled by drm_writeback_connector_init (and kzalloc)

> +
> +	return &mdp5_wb->base;
> +}
> diff --git a/drivers/gpu/drm/msm/dsi/dsi_host.c b/drivers/gpu/drm/msm/dsi/dsi_host.c
> index 7a03a9489708..422f524f7562 100644
> --- a/drivers/gpu/drm/msm/dsi/dsi_host.c
> +++ b/drivers/gpu/drm/msm/dsi/dsi_host.c
> @@ -741,7 +741,7 @@ static void dsi_intr_ctrl(struct msm_dsi_host *msm_host, u32 mask, int enable)
>  	else
>  		intr &= ~mask;
>  
> -	DBG("intr=%x enable=%d", intr, enable);
> +	VERB("intr=%x enable=%d", intr, enable);
>  
>  	dsi_write(msm_host, REG_DSI_INTR_CTRL, intr);
>  	spin_unlock_irqrestore(&msm_host->intr_lock, flags);
> @@ -1465,7 +1465,7 @@ static irqreturn_t dsi_host_irq(int irq, void *ptr)
>  	dsi_write(msm_host, REG_DSI_INTR_CTRL, isr);
>  	spin_unlock_irqrestore(&msm_host->intr_lock, flags);
>  
> -	DBG("isr=0x%x, id=%d", isr, msm_host->id);
> +	VERB("isr=0x%x, id=%d", isr, msm_host->id);
>  
>  	if (isr & DSI_IRQ_ERROR)
>  		dsi_error(msm_host);
> -- 
> 2.14.3
> 

-- 
Sean Paul, Software Engineer, Google / Chromium OS

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [RFC 3/4] drm/msm/mdp5: add config for writeback pipes
  2018-02-23 13:17     ` Rob Clark
@ 2018-02-23 16:32       ` Sean Paul
  -1 siblings, 0 replies; 41+ messages in thread
From: Sean Paul @ 2018-02-23 16:32 UTC (permalink / raw)
  To: Rob Clark
  Cc: Neil Armstrong, David Airlie, linux-arm-msm, Liviu Dudau,
	linux-kernel, dri-devel, Daniel Vetter, freedreno

On Fri, Feb 23, 2018 at 08:17:53AM -0500, Rob Clark wrote:
> Note there seems to be a slight disagreement between public 8x16 HRD
> (which claims WB2 has offset of 0x65000, relative to start of MDP), and
> reality (which claims WB2 has offset of 0x64800).  I sided with reality.
> 
> There should also be a WB0 attached to LM0 (which routes to DSI
> interface).  It isn't clear if this can be used at the same time as
> output to DSI, which would be hugely useful.  I was unable to get this
> to work (with HDMI bridge chip on db410c, so DSI in video mode).
> 
> This will be needed to implement writeback support, but also useful
> to remove a manual hack to the generated headers (since rnndb register
> docs for WB had been merged long ago).
> 
> Also fixes LM3 offset.
> 
> Signed-off-by: Rob Clark <robdclark@gmail.com>

I'm not familiar with the hw, but I don't see any programming errors, so:

Reviewed-by: Sean Paul <seanpaul@chromium.org>

> ---
>  drivers/gpu/drm/msm/disp/mdp5/mdp5.xml.h |  2 --
>  drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.c | 17 +++++++++++------
>  drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.h | 11 +++++++++++
>  drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c |  1 +
>  4 files changed, 23 insertions(+), 8 deletions(-)
> 
> diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5.xml.h b/drivers/gpu/drm/msm/disp/mdp5/mdp5.xml.h
> index d9c10e02ee41..bebcbabb1fe4 100644
> --- a/drivers/gpu/drm/msm/disp/mdp5/mdp5.xml.h
> +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5.xml.h
> @@ -1391,13 +1391,11 @@ static inline uint32_t REG_MDP5_PP_FBC_LOSSY_MODE(uint32_t i0) { return 0x000000
>  static inline uint32_t __offset_WB(uint32_t idx)
>  {
>  	switch (idx) {
> -#if 0  /* TEMPORARY until patch that adds wb.base[] is merged */
>  		case 0: return (mdp5_cfg->wb.base[0]);
>  		case 1: return (mdp5_cfg->wb.base[1]);
>  		case 2: return (mdp5_cfg->wb.base[2]);
>  		case 3: return (mdp5_cfg->wb.base[3]);
>  		case 4: return (mdp5_cfg->wb.base[4]);
> -#endif
>  		default: return INVALID_IDX(idx);
>  	}
>  }
> diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.c
> index 824067d2d427..f92e68cdeeef 100644
> --- a/drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.c
> +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.c
> @@ -300,8 +300,8 @@ const struct mdp5_cfg_hw msm8x16_config = {
>  		},
>  	},
>  	.ctl = {
> -		.count = 5,
> -		.base = { 0x01000, 0x01200, 0x01400, 0x01600, 0x01800 },
> +		.count = 3,
> +		.base = { 0x01000, 0x01200, 0x01400 },
>  		.flush_hw_mask = 0x4003ffff,
>  	},
>  	.pipe_vig = {
> @@ -324,7 +324,7 @@ const struct mdp5_cfg_hw msm8x16_config = {
>  	},
>  	.lm = {
>  		.count = 2, /* LM0 and LM3 */
> -		.base = { 0x44000, 0x47000 },
> +		.base = { [0] = 0x44000, [3] = 0x47000 },
>  		.instances = {
>  				{ .id = 0, .pp = 0, .dspp = 0,
>  				  .caps = MDP_LM_CAP_DISPLAY, },
> @@ -338,12 +338,17 @@ const struct mdp5_cfg_hw msm8x16_config = {
>  	.dspp = {
>  		.count = 1,
>  		.base = { 0x54000 },
> -
> +	},
> +	.wb = {
> +		.count = 1,
> +		.base = { [0] = 0x64000, [2] = 0x64800 },
> +		.instances = {
> +			{ .id = 2, .lm = 3 },
> +		},
>  	},
>  	.intf = {
> -		.base = { 0x00000, 0x6a800 },
> +		.base = { 0x6a000, 0x6a800, 0x6b000, 0x6b800 },
>  		.connect = {
> -			[0] = INTF_DISABLED,
>  			[1] = INTF_DSI,
>  		},
>  	},
> diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.h b/drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.h
> index 75910d0f2f4c..2e529fb2f9ee 100644
> --- a/drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.h
> +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.h
> @@ -77,6 +77,16 @@ struct mdp5_mdp_block {
>  	uint32_t caps;			/* MDP capabilities: MDP_CAP_xxx bits */
>  };
>  
> +struct mdp5_wb_instance {
> +	int id;
> +	int lm;
> +};
> +
> +struct mdp5_wb_block {
> +	MDP5_SUB_BLOCK_DEFINITION;
> +	struct mdp5_wb_instance instances[MAX_BASES];
> +};
> +
>  #define MDP5_INTF_NUM_MAX	5
>  
>  struct mdp5_intf_block {
> @@ -100,6 +110,7 @@ struct mdp5_cfg_hw {
>  	struct mdp5_sub_block pp;
>  	struct mdp5_sub_block dsc;
>  	struct mdp5_sub_block cdm;
> +	struct mdp5_wb_block wb;
>  	struct mdp5_intf_block intf;
>  
>  	uint32_t max_clk;
> diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c
> index 6d8e3a9a6fc0..1f44d8f15ce1 100644
> --- a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c
> +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c
> @@ -652,6 +652,7 @@ struct msm_kms *mdp5_kms_init(struct drm_device *dev)
>  	pm_runtime_get_sync(&pdev->dev);
>  	for (i = 0; i < MDP5_INTF_NUM_MAX; i++) {
>  		if (mdp5_cfg_intf_is_virtual(config->hw->intf.connect[i]) ||
> +		    (config->hw->intf.connect[i] == INTF_DISABLED) ||
>  		    !config->hw->intf.base[i])
>  			continue;
>  		mdp5_write(mdp5_kms, REG_MDP5_INTF_TIMING_ENGINE_EN(i), 0);
> -- 
> 2.14.3
> 

-- 
Sean Paul, Software Engineer, Google / Chromium OS
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [RFC 3/4] drm/msm/mdp5: add config for writeback pipes
@ 2018-02-23 16:32       ` Sean Paul
  0 siblings, 0 replies; 41+ messages in thread
From: Sean Paul @ 2018-02-23 16:32 UTC (permalink / raw)
  To: Rob Clark
  Cc: dri-devel, freedreno, linux-arm-msm, Brian Starkey, Liviu Dudau,
	David Airlie, Sean Paul, Archit Taneja, Daniel Vetter,
	Neil Armstrong, Ville Syrjälä,
	linux-kernel

On Fri, Feb 23, 2018 at 08:17:53AM -0500, Rob Clark wrote:
> Note there seems to be a slight disagreement between public 8x16 HRD
> (which claims WB2 has offset of 0x65000, relative to start of MDP), and
> reality (which claims WB2 has offset of 0x64800).  I sided with reality.
> 
> There should also be a WB0 attached to LM0 (which routes to DSI
> interface).  It isn't clear if this can be used at the same time as
> output to DSI, which would be hugely useful.  I was unable to get this
> to work (with HDMI bridge chip on db410c, so DSI in video mode).
> 
> This will be needed to implement writeback support, but also useful
> to remove a manual hack to the generated headers (since rnndb register
> docs for WB had been merged long ago).
> 
> Also fixes LM3 offset.
> 
> Signed-off-by: Rob Clark <robdclark@gmail.com>

I'm not familiar with the hw, but I don't see any programming errors, so:

Reviewed-by: Sean Paul <seanpaul@chromium.org>

> ---
>  drivers/gpu/drm/msm/disp/mdp5/mdp5.xml.h |  2 --
>  drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.c | 17 +++++++++++------
>  drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.h | 11 +++++++++++
>  drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c |  1 +
>  4 files changed, 23 insertions(+), 8 deletions(-)
> 
> diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5.xml.h b/drivers/gpu/drm/msm/disp/mdp5/mdp5.xml.h
> index d9c10e02ee41..bebcbabb1fe4 100644
> --- a/drivers/gpu/drm/msm/disp/mdp5/mdp5.xml.h
> +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5.xml.h
> @@ -1391,13 +1391,11 @@ static inline uint32_t REG_MDP5_PP_FBC_LOSSY_MODE(uint32_t i0) { return 0x000000
>  static inline uint32_t __offset_WB(uint32_t idx)
>  {
>  	switch (idx) {
> -#if 0  /* TEMPORARY until patch that adds wb.base[] is merged */
>  		case 0: return (mdp5_cfg->wb.base[0]);
>  		case 1: return (mdp5_cfg->wb.base[1]);
>  		case 2: return (mdp5_cfg->wb.base[2]);
>  		case 3: return (mdp5_cfg->wb.base[3]);
>  		case 4: return (mdp5_cfg->wb.base[4]);
> -#endif
>  		default: return INVALID_IDX(idx);
>  	}
>  }
> diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.c
> index 824067d2d427..f92e68cdeeef 100644
> --- a/drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.c
> +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.c
> @@ -300,8 +300,8 @@ const struct mdp5_cfg_hw msm8x16_config = {
>  		},
>  	},
>  	.ctl = {
> -		.count = 5,
> -		.base = { 0x01000, 0x01200, 0x01400, 0x01600, 0x01800 },
> +		.count = 3,
> +		.base = { 0x01000, 0x01200, 0x01400 },
>  		.flush_hw_mask = 0x4003ffff,
>  	},
>  	.pipe_vig = {
> @@ -324,7 +324,7 @@ const struct mdp5_cfg_hw msm8x16_config = {
>  	},
>  	.lm = {
>  		.count = 2, /* LM0 and LM3 */
> -		.base = { 0x44000, 0x47000 },
> +		.base = { [0] = 0x44000, [3] = 0x47000 },
>  		.instances = {
>  				{ .id = 0, .pp = 0, .dspp = 0,
>  				  .caps = MDP_LM_CAP_DISPLAY, },
> @@ -338,12 +338,17 @@ const struct mdp5_cfg_hw msm8x16_config = {
>  	.dspp = {
>  		.count = 1,
>  		.base = { 0x54000 },
> -
> +	},
> +	.wb = {
> +		.count = 1,
> +		.base = { [0] = 0x64000, [2] = 0x64800 },
> +		.instances = {
> +			{ .id = 2, .lm = 3 },
> +		},
>  	},
>  	.intf = {
> -		.base = { 0x00000, 0x6a800 },
> +		.base = { 0x6a000, 0x6a800, 0x6b000, 0x6b800 },
>  		.connect = {
> -			[0] = INTF_DISABLED,
>  			[1] = INTF_DSI,
>  		},
>  	},
> diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.h b/drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.h
> index 75910d0f2f4c..2e529fb2f9ee 100644
> --- a/drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.h
> +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.h
> @@ -77,6 +77,16 @@ struct mdp5_mdp_block {
>  	uint32_t caps;			/* MDP capabilities: MDP_CAP_xxx bits */
>  };
>  
> +struct mdp5_wb_instance {
> +	int id;
> +	int lm;
> +};
> +
> +struct mdp5_wb_block {
> +	MDP5_SUB_BLOCK_DEFINITION;
> +	struct mdp5_wb_instance instances[MAX_BASES];
> +};
> +
>  #define MDP5_INTF_NUM_MAX	5
>  
>  struct mdp5_intf_block {
> @@ -100,6 +110,7 @@ struct mdp5_cfg_hw {
>  	struct mdp5_sub_block pp;
>  	struct mdp5_sub_block dsc;
>  	struct mdp5_sub_block cdm;
> +	struct mdp5_wb_block wb;
>  	struct mdp5_intf_block intf;
>  
>  	uint32_t max_clk;
> diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c
> index 6d8e3a9a6fc0..1f44d8f15ce1 100644
> --- a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c
> +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c
> @@ -652,6 +652,7 @@ struct msm_kms *mdp5_kms_init(struct drm_device *dev)
>  	pm_runtime_get_sync(&pdev->dev);
>  	for (i = 0; i < MDP5_INTF_NUM_MAX; i++) {
>  		if (mdp5_cfg_intf_is_virtual(config->hw->intf.connect[i]) ||
> +		    (config->hw->intf.connect[i] == INTF_DISABLED) ||
>  		    !config->hw->intf.base[i])
>  			continue;
>  		mdp5_write(mdp5_kms, REG_MDP5_INTF_TIMING_ENGINE_EN(i), 0);
> -- 
> 2.14.3
> 

-- 
Sean Paul, Software Engineer, Google / Chromium OS

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [RFC 1/4] drm: Add writeback connector type
  2018-02-23 16:21           ` Liviu Dudau
@ 2018-02-23 16:39               ` Sean Paul
  -1 siblings, 0 replies; 41+ messages in thread
From: Sean Paul @ 2018-02-23 16:39 UTC (permalink / raw)
  To: Liviu Dudau
  Cc: Jonathan Corbet, David Airlie,
	linux-arm-msm-u79uwXL29TY76Z2rM5mHXA,
	linux-doc-u79uwXL29TY76Z2rM5mHXA, Maarten Lankhorst,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, Rob Clark, Sean Paul,
	Gustavo Padovan, Mihail Atanassov,
	freedreno-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, Brian Starkey

On Fri, Feb 23, 2018 at 04:21:05PM +0000, Liviu Dudau wrote:
> On Fri, Feb 23, 2018 at 10:59:35AM -0500, Sean Paul wrote:
> > On Fri, Feb 23, 2018 at 08:17:51AM -0500, Rob Clark wrote:
> > > From: Brian Starkey <brian.starkey@arm.com>
> > > 
> > > Writeback connectors represent writeback engines which can write the
> > > CRTC output to a memory framebuffer. Add a writeback connector type and
> > > related support functions.
> > > 
> > > Drivers should initialize a writeback connector with
> > > drm_writeback_connector_init() which takes care of setting up all the
> > > writeback-specific details on top of the normal functionality of
> > > drm_connector_init().
> > > 
> > > Writeback connectors have a WRITEBACK_FB_ID property, used to set the
> > > output framebuffer, and a WRITEBACK_PIXEL_FORMATS blob used to expose the
> > > supported writeback formats to userspace.
> > > 
> > > When a framebuffer is attached to a writeback connector with the
> > > WRITEBACK_FB_ID property, it is used only once (for the commit in which
> > > it was included), and userspace can never read back the value of
> > > WRITEBACK_FB_ID. WRITEBACK_FB_ID can only be set if the connector is
> > > attached to a CRTC.
> > > 
> > > Changes since v1:
> > >  - Added drm_writeback.c + documentation
> > >  - Added helper to initialize writeback connector in one go
> > >  - Added core checks
> > >  - Squashed into a single commit
> > >  - Dropped the client cap
> > >  - Writeback framebuffers are no longer persistent
> > > 
> > > Changes since v2:
> > >  Daniel Vetter:
> > >  - Subclass drm_connector to drm_writeback_connector
> > >  - Relax check to allow CRTC to be set without an FB
> > >  - Add some writeback_ prefixes
> > >  - Drop PIXEL_FORMATS_SIZE property, as it was unnecessary
> > >  Gustavo Padovan:
> > >  - Add drm_writeback_job to handle writeback signalling centrally
> > > 
> > > Changes since v3:
> > >  - Rebased
> > >  - Rename PIXEL_FORMATS -> WRITEBACK_PIXEL_FORMATS
> > > 
> > > Changes since v4:
> > >  - Added atomic_commit() vfunc to connector helper funcs, so that
> > >    writeback jobs are committed from atomic helpers
> > > 
> > > Signed-off-by: Brian Starkey <brian.starkey@arm.com>
> > > [rebased and fixed conflicts]
> > > Signed-off-by: Mihail Atanassov <mihail.atanassov@arm.com>
> > > Signed-off-by: Liviu Dudau <liviu.dudau@arm.com>
> > > [rebased and added atomic_commit() vfunc for writeback jobs]
> > > Signed-off-by: Rob Clark <robdclark@gmail.com>
> > > ---
> > >  Documentation/gpu/drm-kms.rst            |   9 ++
> > >  drivers/gpu/drm/Makefile                 |   2 +-
> > >  drivers/gpu/drm/drm_atomic.c             | 130 ++++++++++++++++
> > >  drivers/gpu/drm/drm_atomic_helper.c      |  30 ++++
> > >  drivers/gpu/drm/drm_connector.c          |   4 +-
> > >  drivers/gpu/drm/drm_writeback.c          | 257 +++++++++++++++++++++++++++++++
> > >  include/drm/drm_atomic.h                 |   3 +
> > >  include/drm/drm_connector.h              |  13 ++
> > >  include/drm/drm_mode_config.h            |  14 ++
> > >  include/drm/drm_modeset_helper_vtables.h |  11 ++
> > >  include/drm/drm_writeback.h              |  89 +++++++++++
> > >  include/uapi/drm/drm_mode.h              |   1 +
> > >  12 files changed, 561 insertions(+), 2 deletions(-)
> > >  create mode 100644 drivers/gpu/drm/drm_writeback.c
> > >  create mode 100644 include/drm/drm_writeback.h
> > > 
> > > diff --git a/Documentation/gpu/drm-kms.rst b/Documentation/gpu/drm-kms.rst
> > > index 2dcf5b42015d..e7590dd2f71e 100644
> > > --- a/Documentation/gpu/drm-kms.rst
> > > +++ b/Documentation/gpu/drm-kms.rst
> > > @@ -370,6 +370,15 @@ Connector Functions Reference
> > >  .. kernel-doc:: drivers/gpu/drm/drm_connector.c
> > >     :export:
> > >  
> > > +Writeback Connectors
> > > +--------------------
> > > +
> > > +.. kernel-doc:: drivers/gpu/drm/drm_writeback.c
> > > +  :doc: overview
> > > +
> > > +.. kernel-doc:: drivers/gpu/drm/drm_writeback.c
> > > +  :export:
> > > +
> > >  Encoder Abstraction
> > >  ===================
> > >  
> > > diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> > > index 50093ff4479b..3d708959b224 100644
> > > --- a/drivers/gpu/drm/Makefile
> > > +++ b/drivers/gpu/drm/Makefile
> > > @@ -18,7 +18,7 @@ drm-y       :=	drm_auth.o drm_bufs.o drm_cache.o \
> > >  		drm_encoder.o drm_mode_object.o drm_property.o \
> > >  		drm_plane.o drm_color_mgmt.o drm_print.o \
> > >  		drm_dumb_buffers.o drm_mode_config.o drm_vblank.o \
> > > -		drm_syncobj.o drm_lease.o
> > > +		drm_syncobj.o drm_lease.o drm_writeback.o
> > >  
> > >  drm-$(CONFIG_DRM_LIB_RANDOM) += lib/drm_random.o
> > >  drm-$(CONFIG_DRM_VM) += drm_vm.o
> > > diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
> > > index 46733d534587..019f131fe8be 100644
> > > --- a/drivers/gpu/drm/drm_atomic.c
> > > +++ b/drivers/gpu/drm/drm_atomic.c
> > > @@ -30,6 +30,7 @@
> > >  #include <drm/drm_atomic.h>
> > >  #include <drm/drm_mode.h>
> > >  #include <drm/drm_print.h>
> > > +#include <drm/drm_writeback.h>
> > >  #include <linux/sync_file.h>
> > >  
> > >  #include "drm_crtc_internal.h"
> > > @@ -638,6 +639,46 @@ static void drm_atomic_crtc_print_state(struct drm_printer *p,
> > >  		crtc->funcs->atomic_print_state(p, state);
> > >  }
> > >  
> > > +/**
> > > + * drm_atomic_connector_check - check connector state
> > > + * @connector: connector to check
> > > + * @state: connector state to check
> > > + *
> > > + * Provides core sanity checks for connector state.
> > > + *
> > > + * RETURNS:
> > > + * Zero on success, error code on failure
> > > + */
> > > +static int drm_atomic_connector_check(struct drm_connector *connector,
> > > +		struct drm_connector_state *state)
> > > +{
> > > +	struct drm_crtc_state *crtc_state;
> > > +	struct drm_writeback_job *writeback_job = state->writeback_job;
> > > +
> > > +	if ((connector->connector_type != DRM_MODE_CONNECTOR_WRITEBACK) ||
> > > +	    !writeback_job)
> > > +		return 0;
> > > +
> > > +	if (writeback_job->fb && !state->crtc) {
> > > +		DRM_DEBUG_ATOMIC("[CONNECTOR:%d:%s] framebuffer without CRTC\n",
> > > +				 connector->base.id, connector->name);
> > > +		return -EINVAL;
> > > +	}
> > > +
> > > +	if (state->crtc)
> > > +		crtc_state = drm_atomic_get_existing_crtc_state(state->state,
> > > +								state->crtc);
> > > +
> > > +	if (writeback_job->fb && !crtc_state->active) {
> > 
> > Checking for writeback_job->fb is redundant here and above since it's checked
> > first thing as early exit criteria.
> 
> I somehow don't see that check. We check if (!writebac_job) and then all
> other checks are logic ands with other conditions, so I think the check
> here is still relevant.

Ah, yeah, the first check doesn't check for ->fb (I imagined it on the end of
writeback_job. At any rate, if you add a check for !->fb in the first conditional
you don't need it here or in the second check.

> 
> > 
> > > +		DRM_DEBUG_ATOMIC("[CONNECTOR:%d:%s] has framebuffer, but [CRTC:%d] is off\n",
> > > +				 connector->base.id, connector->name,
> > > +				 state->crtc->base.id);
> > > +		return -EINVAL;
> > > +	}
> > > +
> > > +	return 0;
> > > +}
> > > +
> > >  /**
> > >   * drm_atomic_get_plane_state - get plane state
> > >   * @state: global atomic state object
> > > @@ -1230,6 +1271,12 @@ static int drm_atomic_connector_set_property(struct drm_connector *connector,
> > >  			return -EINVAL;
> > >  		}
> > >  		state->content_protection = val;
> > > +	} else if (property == config->writeback_fb_id_property) {
> > > +		struct drm_framebuffer *fb = drm_framebuffer_lookup(dev, NULL, val);
> > > +		int ret = drm_atomic_set_writeback_fb_for_connector(state, fb);
> > > +		if (fb)
> > > +			drm_framebuffer_unreference(fb);
> > > +		return ret;
> > >  	} else if (connector->funcs->atomic_set_property) {
> > >  		return connector->funcs->atomic_set_property(connector,
> > >  				state, property, val);
> > > @@ -1311,6 +1358,9 @@ drm_atomic_connector_get_property(struct drm_connector *connector,
> > >  		*val = state->scaling_mode;
> > >  	} else if (property == connector->content_protection_property) {
> > >  		*val = state->content_protection;
> > > +	} else if (property == config->writeback_fb_id_property) {
> > > +		/* Writeback framebuffer is one-shot, write and forget */
> > > +		*val = 0;
> > >  	} else if (connector->funcs->atomic_get_property) {
> > >  		return connector->funcs->atomic_get_property(connector,
> > >  				state, property, val);
> > > @@ -1518,6 +1568,75 @@ drm_atomic_set_crtc_for_connector(struct drm_connector_state *conn_state,
> > >  }
> > >  EXPORT_SYMBOL(drm_atomic_set_crtc_for_connector);
> > >  
> > > +/*
> > > + * drm_atomic_get_writeback_job - return or allocate a writeback job
> > > + * @conn_state: Connector state to get the job for
> > > + *
> > > + * Writeback jobs have a different lifetime to the atomic state they are
> > > + * associated with. This convenience function takes care of allocating a job
> > > + * if there isn't yet one associated with the connector state, otherwise
> > > + * it just returns the existing job.
> > > + *
> > > + * Returns: The writeback job for the given connector state
> > > + */
> > > +static struct drm_writeback_job *
> > > +drm_atomic_get_writeback_job(struct drm_connector_state *conn_state)
> > > +{
> > > +	WARN_ON(conn_state->connector->connector_type !=
> > > +		DRM_MODE_CONNECTOR_WRITEBACK);
> > > +
> > > +	if (!conn_state->writeback_job)
> > > +		conn_state->writeback_job =
> > > +			kzalloc(sizeof(*conn_state->writeback_job), GFP_KERNEL);
> > > +
> > > +	return conn_state->writeback_job;
> > > +}
> > > +
> > > +/**
> > > + * drm_atomic_set_writeback_fb_for_connector - set writeback framebuffer
> > > + * @conn_state: atomic state object for the connector
> > > + * @fb: fb to use for the connector
> > > + *
> > > + * This is used to set the framebuffer for a writeback connector, which outputs
> > > + * to a buffer instead of an actual physical connector.
> > > + * Changing the assigned framebuffer requires us to grab a reference to the new
> > > + * fb and drop the reference to the old fb, if there is one. This function
> > > + * takes care of all these details besides updating the pointer in the
> > > + * state object itself.
> > > + *
> > > + * Note: The only way conn_state can already have an fb set is if the commit
> > > + * sets the property more than once.
> > > + *
> > > + * See also: drm_writeback_connector_init()
> > > + *
> > > + * Returns: 0 on success
> > > + */
> > > +int drm_atomic_set_writeback_fb_for_connector(
> > > +		struct drm_connector_state *conn_state,
> > > +		struct drm_framebuffer *fb)
> > > +{
> > > +	struct drm_writeback_job *job =
> > > +		drm_atomic_get_writeback_job(conn_state);
> > > +	if (!job)
> > > +		return -ENOMEM;
> > > +
> > > +	if (job->fb)
> > > +		drm_framebuffer_unreference(job->fb);
> > > +	if (fb)
> > > +		drm_framebuffer_reference(fb);
> > > +	job->fb = fb;
> > > +
> > > +	if (fb)
> > > +		DRM_DEBUG_ATOMIC("Set [FB:%d] for connector state %p\n",
> > > +				 fb->base.id, conn_state);
> > > +	else
> > > +		DRM_DEBUG_ATOMIC("Set [NOFB] for connector state %p\n",
> > > +				 conn_state);
> > > +
> > > +	return 0;
> > > +}
> > > +EXPORT_SYMBOL(drm_atomic_set_writeback_fb_for_connector);
> > > +
> > >  /**
> > >   * drm_atomic_add_affected_connectors - add connectors for crtc
> > >   * @state: atomic state
> > > @@ -1636,6 +1755,8 @@ int drm_atomic_check_only(struct drm_atomic_state *state)
> > >  	struct drm_plane_state *plane_state;
> > >  	struct drm_crtc *crtc;
> > >  	struct drm_crtc_state *crtc_state;
> > > +	struct drm_connector *conn;
> > > +	struct drm_connector_state *conn_state;
> > >  	int i, ret = 0;
> > >  
> > >  	DRM_DEBUG_ATOMIC("checking %p\n", state);
> > > @@ -1658,6 +1779,15 @@ int drm_atomic_check_only(struct drm_atomic_state *state)
> > >  		}
> > >  	}
> > >  
> > > +	for_each_new_connector_in_state(state, conn, conn_state, i) {
> > > +		ret = drm_atomic_connector_check(conn, conn_state);
> > > +		if (ret) {
> > > +			DRM_DEBUG_ATOMIC("[CONNECTOR:%d:%s] atomic core check failed\n",
> > > +					 conn->base.id, conn->name);
> > > +			return ret;
> > > +		}
> > > +	}
> > > +
> > >  	if (config->funcs->atomic_check)
> > >  		ret = config->funcs->atomic_check(state->dev, state);
> > >  
> > > diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c
> > > index ae3cbfe9e01c..12b910755d84 100644
> > > --- a/drivers/gpu/drm/drm_atomic_helper.c
> > > +++ b/drivers/gpu/drm/drm_atomic_helper.c
> > > @@ -30,6 +30,7 @@
> > >  #include <drm/drm_plane_helper.h>
> > >  #include <drm/drm_crtc_helper.h>
> > >  #include <drm/drm_atomic_helper.h>
> > > +#include <drm/drm_writeback.h>
> > >  #include <linux/dma-fence.h>
> > >  
> > >  #include "drm_crtc_helper_internal.h"
> > > @@ -1159,6 +1160,27 @@ void drm_atomic_helper_commit_modeset_disables(struct drm_device *dev,
> > >  }
> > >  EXPORT_SYMBOL(drm_atomic_helper_commit_modeset_disables);
> > >  
> > > +static void commit_writebacks(struct drm_device *dev, struct drm_atomic_state *old_state)
> > > +{
> > > +	struct drm_connector *connector;
> > > +	struct drm_connector_state *new_conn_state;
> > > +	int i;
> > > +
> > > +	for_each_new_connector_in_state(old_state, connector, new_conn_state, i) {
> > > +		const struct drm_connector_helper_funcs *funcs;
> > > +
> > > +		funcs = connector->helper_private;
> > > +
> > > +		if (new_conn_state->writeback_job &&
> > > +		    new_conn_state->writeback_job->fb) {
> > > +			WARN_ON(connector->connector_type !=
> > > +				DRM_MODE_CONNECTOR_WRITEBACK);
> > > +			funcs->atomic_commit(connector,
> > > +					     new_conn_state->writeback_job);
> > > +		}
> > > +	}
> > > +}
> > > +
> > >  /**
> > >   * drm_atomic_helper_commit_modeset_enables - modeset commit to enable outputs
> > >   * @dev: DRM device
> > > @@ -1238,6 +1260,8 @@ void drm_atomic_helper_commit_modeset_enables(struct drm_device *dev,
> > >  
> > >  		drm_bridge_enable(encoder->bridge);
> > >  	}
> > > +
> > > +	commit_writebacks(dev, old_state);
> > >  }
> > >  EXPORT_SYMBOL(drm_atomic_helper_commit_modeset_enables);
> > >  
> > > @@ -3627,6 +3651,9 @@ __drm_atomic_helper_connector_duplicate_state(struct drm_connector *connector,
> > >  	if (state->crtc)
> > >  		drm_connector_get(connector);
> > >  	state->commit = NULL;
> > > +
> > > +	/* Don't copy over a writeback job, they are used only once */
> > > +	state->writeback_job = NULL;
> > >  }
> > >  EXPORT_SYMBOL(__drm_atomic_helper_connector_duplicate_state);
> > >  
> > > @@ -3756,6 +3783,9 @@ __drm_atomic_helper_connector_destroy_state(struct drm_connector_state *state)
> > >  
> > >  	if (state->commit)
> > >  		drm_crtc_commit_put(state->commit);
> > > +
> > > +	if (state->writeback_job)
> > > +		drm_writeback_cleanup_job(state->writeback_job);
> > >  }
> > >  EXPORT_SYMBOL(__drm_atomic_helper_connector_destroy_state);
> > >  
> > > diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c
> > > index 16b9c3810af2..add47f06ae70 100644
> > > --- a/drivers/gpu/drm/drm_connector.c
> > > +++ b/drivers/gpu/drm/drm_connector.c
> > > @@ -87,6 +87,7 @@ static struct drm_conn_prop_enum_list drm_connector_enum_list[] = {
> > >  	{ DRM_MODE_CONNECTOR_VIRTUAL, "Virtual" },
> > >  	{ DRM_MODE_CONNECTOR_DSI, "DSI" },
> > >  	{ DRM_MODE_CONNECTOR_DPI, "DPI" },
> > > +	{ DRM_MODE_CONNECTOR_WRITEBACK, "Writeback" },
> > >  };
> > >  
> > >  void drm_connector_ida_init(void)
> > > @@ -249,7 +250,8 @@ int drm_connector_init(struct drm_device *dev,
> > >  	config->num_connector++;
> > >  	spin_unlock_irq(&config->connector_list_lock);
> > >  
> > > -	if (connector_type != DRM_MODE_CONNECTOR_VIRTUAL)
> > > +	if (connector_type != DRM_MODE_CONNECTOR_VIRTUAL &&
> > > +	    connector_type != DRM_MODE_CONNECTOR_WRITEBACK)
> > >  		drm_object_attach_property(&connector->base,
> > >  					      config->edid_property,
> > >  					      0);
> > > diff --git a/drivers/gpu/drm/drm_writeback.c b/drivers/gpu/drm/drm_writeback.c
> > > new file mode 100644
> > > index 000000000000..da61f929cbc3
> > > --- /dev/null
> > > +++ b/drivers/gpu/drm/drm_writeback.c
> > > @@ -0,0 +1,257 @@
> > > +/*
> > > + * (C) COPYRIGHT 2016 ARM Limited. All rights reserved.
> > > + * Author: Brian Starkey <brian.starkey@arm.com>
> > > + *
> > > + * This program is free software and is provided to you under the terms of the
> > > + * GNU General Public License version 2 as published by the Free Software
> > > + * Foundation, and any use by you of this program is subject to the terms
> > > + * of such GNU licence.
> > > + */
> > > +
> > > +#include <drm/drm_crtc.h>
> > > +#include <drm/drm_modeset_helper_vtables.h>
> > > +#include <drm/drm_property.h>
> > > +#include <drm/drm_writeback.h>
> > > +#include <drm/drmP.h>
> > > +
> > > +/**
> > > + * DOC: overview
> > > + *
> > > + * Writeback connectors are used to expose hardware which can write the output
> > > + * from a CRTC to a memory buffer. They are used and act similarly to other
> > > + * types of connectors, with some important differences:
> > > + *  - Writeback connectors don't provide a way to output visually to the user.
> > > + *  - Writeback connectors should always report as "disconnected" (so that
> > > + *    clients which don't understand them will ignore them).
> > 
> > Have we considered hiding writeback behind a client cap instead?
> 
> Yes, changelog says that the client cap was dropped in v2 based on
> feedback (as far as I remember). 
> 
> > 
> > > + *  - Writeback connectors don't have EDID.
> > > + *
> > > + * A framebuffer may only be attached to a writeback connector when the
> > > + * connector is attached to a CRTC. The WRITEBACK_FB_ID property which sets the
> > > + * framebuffer applies only to a single commit (see below). A framebuffer may
> > > + * not be attached while the CRTC is off.
> > > + *
> > > + * Writeback connectors have some additional properties, which userspace
> > > + * can use to query and control them:
> > > + *
> > > + *  "WRITEBACK_FB_ID":
> > > + *	Write-only object property storing a DRM_MODE_OBJECT_FB: it stores the
> > > + *	framebuffer to be written by the writeback connector. This property is
> > > + *	similar to the FB_ID property on planes, but will always read as zero
> > > + *	and is not preserved across commits.
> > > + *	Userspace must set this property to an output buffer every time it
> > > + *	wishes the buffer to get filled.
> > > + *
> > > + *  "WRITEBACK_PIXEL_FORMATS":
> > > + *	Immutable blob property to store the supported pixel formats table. The
> > > + *	data is an array of u32 DRM_FORMAT_* fourcc values.
> > > + *	Userspace can use this blob to find out what pixel formats are supported
> > > + *	by the connector's writeback engine.
> > > + */
> > > +
> > > +static bool create_writeback_properties(struct drm_device *dev)
> > > +{
> > > +	struct drm_property *prop;
> > > +
> > > +	if (!dev->mode_config.writeback_fb_id_property) {
> > 
> > Somewhat an aside, but we should probably come up with more formal rules on
> > where to stick properties. We have some in drm_connector, which I interpret as
> > "connector-only properties", but we also have a bunch in mode_config.
> > 
> > For optional properties which will generally only apply to one connector on a
> > system, I'd be inclined to put it in connector. 
> 
> I can move the writeback-related properties into the connector in the
> next series.
> 

Either way works for me, no need to do this if you're happy with it, I was just
idly musing.

> 
> > 
> > > +		prop = drm_property_create_object(dev, DRM_MODE_PROP_ATOMIC,
> > > +						  "WRITEBACK_FB_ID",
> > > +						  DRM_MODE_OBJECT_FB);
> > > +		if (!prop)
> > > +			return false;
> > > +		dev->mode_config.writeback_fb_id_property = prop;
> > > +	}
> > > +
> > > +	if (!dev->mode_config.writeback_pixel_formats_property) {
> > > +		prop = drm_property_create(dev, DRM_MODE_PROP_BLOB | DRM_MODE_PROP_IMMUTABLE,
> > > +					   "WRITEBACK_PIXEL_FORMATS", 0);
> > 
> > Does it make sense to expose the formats if u/s doesn't support atomic and can't
> > use writeback? Perhaps this should also be DRM_MODE_PROP_ATOMIC.
> 
> Agree, probably all the writeback properties should only be available if
> the client selects DRM_MODE_PROP_ATOMIC.
> 
> > 
> > 
> > > +		if (!prop)
> > > +			return false;
> > > +		dev->mode_config.writeback_pixel_formats_property = prop;
> > > +	}
> > > +
> > > +	return true;
> > > +}
> > > +
> > > +static const struct drm_encoder_funcs drm_writeback_encoder_funcs = {
> > > +	.destroy = drm_encoder_cleanup,
> > > +};
> > > +
> > > +/**
> > > + * drm_writeback_connector_init - Initialize a writeback connector and its properties
> > > + * @dev: DRM device
> > > + * @wb_connector: Writeback connector to initialize
> > > + * @con_funcs: Connector funcs vtable
> > > + * @enc_helper_funcs: Encoder helper funcs vtable to be used by the internal encoder
> > > + * @formats: Array of supported pixel formats for the writeback engine
> > > + * @n_formats: Length of the formats array
> > > + *
> > > + * This function creates the writeback-connector-specific properties if they
> > > + * have not been already created, initializes the connector as
> > > + * type DRM_MODE_CONNECTOR_WRITEBACK, and correctly initializes the property
> > > + * values. It will also create an internal encoder associated with the
> > > + * drm_writeback_connector and set it to use the @enc_helper_funcs vtable for
> > > + * the encoder helper.
> > > + *
> > > + * Drivers should always use this function instead of drm_connector_init() to
> > > + * set up writeback connectors.
> > > + *
> > > + * Returns: 0 on success, or a negative error code
> > > + */
> > > +int drm_writeback_connector_init(struct drm_device *dev,
> > > +				 struct drm_writeback_connector *wb_connector,
> > > +				 const struct drm_connector_funcs *con_funcs,
> > > +				 const struct drm_encoder_helper_funcs *enc_helper_funcs,
> > > +				 const u32 *formats, int n_formats)
> > > +{
> > > +	int ret;
> > > +	struct drm_property_blob *blob;
> > > +	struct drm_connector *connector = &wb_connector->base;
> > > +	struct drm_mode_config *config = &dev->mode_config;
> > > +
> > > +	if (!create_writeback_properties(dev))
> > > +		return -EINVAL;
> > > +
> > > +	blob = drm_property_create_blob(dev, n_formats * sizeof(*formats),
> > > +					formats);
> > > +	if (IS_ERR(blob))
> > > +		return PTR_ERR(blob);
> > > +
> > > +	drm_encoder_helper_add(&wb_connector->encoder, enc_helper_funcs);
> > > +	ret = drm_encoder_init(dev, &wb_connector->encoder,
> > > +			       &drm_writeback_encoder_funcs,
> > > +			       DRM_MODE_ENCODER_VIRTUAL, NULL);
> > > +	if (ret)
> > > +		goto fail;
> > > +
> > > +	connector->interlace_allowed = 0;
> > > +
> > > +	ret = drm_connector_init(dev, connector, con_funcs,
> > > +				 DRM_MODE_CONNECTOR_WRITEBACK);
> > > +	if (ret)
> > > +		goto connector_fail;
> > > +
> > > +	ret = drm_mode_connector_attach_encoder(connector,
> > > +						&wb_connector->encoder);
> > > +	if (ret)
> > > +		goto attach_fail;
> > > +
> > > +	INIT_LIST_HEAD(&wb_connector->job_queue);
> > > +	spin_lock_init(&wb_connector->job_lock);
> > > +
> > > +	drm_object_attach_property(&connector->base,
> > > +				   config->writeback_fb_id_property, 0);
> > > +
> > > +	drm_object_attach_property(&connector->base,
> > > +				   config->writeback_pixel_formats_property,
> > > +				   blob->base.id);
> > > +	wb_connector->pixel_formats_blob_ptr = blob;
> > > +
> > > +	return 0;
> > > +
> > > +attach_fail:
> > > +	drm_connector_cleanup(connector);
> > > +connector_fail:
> > > +	drm_encoder_cleanup(&wb_connector->encoder);
> > > +fail:
> > > +	drm_property_unreference_blob(blob);
> > > +	return ret;
> > > +}
> > > +EXPORT_SYMBOL(drm_writeback_connector_init);
> > > +
> > > +/**
> > > + * drm_writeback_queue_job - Queue a writeback job for later signalling
> > > + * @wb_connector: The writeback connector to queue a job on
> > > + * @job: The job to queue
> > > + *
> > > + * This function adds a job to the job_queue for a writeback connector. It
> > > + * should be considered to take ownership of the writeback job, and so any other
> > > + * references to the job must be cleared after calling this function.
> > > + *
> > > + * Drivers must ensure that for a given writeback connector, jobs are queued in
> > > + * exactly the same order as they will be completed by the hardware (and
> > > + * signaled via drm_writeback_signal_completion).
> > > + *
> > > + * For every call to drm_writeback_queue_job() there must be exactly one call to
> > > + * drm_writeback_signal_completion()
> > > + *
> > > + * See also: drm_writeback_signal_completion()
> > > + */
> > > +void drm_writeback_queue_job(struct drm_writeback_connector *wb_connector,
> > > +			     struct drm_writeback_job *job)
> > > +{
> > > +	unsigned long flags;
> > > +
> > > +	spin_lock_irqsave(&wb_connector->job_lock, flags);
> > > +	list_add_tail(&job->list_entry, &wb_connector->job_queue);
> > > +	spin_unlock_irqrestore(&wb_connector->job_lock, flags);
> > > +}
> > > +EXPORT_SYMBOL(drm_writeback_queue_job);
> > > +
> > > +/**
> > > + * drm_writeback_cleanup_job - Cleanup and free a writeback job
> > > + * @job: The writeback job to free
> > > + *
> > > + * Drops any references held by the writeback job, and frees the structure.
> > > + */
> > > +void drm_writeback_cleanup_job(struct drm_writeback_job *job)
> > > +{
> > > +	if (!job)
> > > +		return;
> > > +
> > > +	if (job->fb)
> > > +		drm_framebuffer_unreference(job->fb);
> > > +	kfree(job);
> > > +}
> > > +EXPORT_SYMBOL(drm_writeback_cleanup_job);
> > > +
> > > +/*
> > > + * @cleanup_work: deferred cleanup of a writeback job
> > > + *
> > > + * The job cannot be cleaned up directly in drm_writeback_signal_completion,
> > > + * because it may be called in interrupt context. Dropping the framebuffer
> > > + * reference can sleep, and so the cleanup is deferred to a workqueue.
> > > + */
> > > +static void cleanup_work(struct work_struct *work)
> > > +{
> > > +	struct drm_writeback_job *job = container_of(work,
> > > +						     struct drm_writeback_job,
> > > +						     cleanup_work);
> > > +	drm_writeback_cleanup_job(job);
> > 
> > TIL that you can free work while using it.
> 
> Was not aware that it is possible. I can update the code to do it.

That's what's happening now, afaict. drm_writeback_cleanup_job() frees job, which
contains work in this function.

Sean

> 
> Thanks for the feedback and review!
> 
> Best regards,
> Liviu
> 
> > 
> > > +}
> > > +
> > > +/**
> > > + * drm_writeback_signal_completion - Signal the completion of a writeback job
> > > + * @wb_connector: The writeback connector whose job is complete
> > > + *
> > > + * Drivers should call this to signal the completion of a previously queued
> > > + * writeback job. It should be called as soon as possible after the hardware
> > > + * has finished writing, and may be called from interrupt context.
> > > + * It is the driver's responsibility to ensure that for a given connector, the
> > > + * hardware completes writeback jobs in the same order as they are queued.
> > > + *
> > > + * Unless the driver is holding its own reference to the framebuffer, it must
> > > + * not be accessed after calling this function.
> > > + *
> > > + * See also: drm_writeback_queue_job()
> > > + */
> > > +void
> > > +drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector)
> > > +{
> > > +	unsigned long flags;
> > > +	struct drm_writeback_job *job;
> > > +
> > > +	spin_lock_irqsave(&wb_connector->job_lock, flags);
> > > +	job = list_first_entry_or_null(&wb_connector->job_queue,
> > > +				       struct drm_writeback_job,
> > > +				       list_entry);
> > > +	if (job)
> > > +		list_del(&job->list_entry);
> > > +	spin_unlock_irqrestore(&wb_connector->job_lock, flags);
> > > +
> > > +	if (WARN_ON(!job))
> > > +		return;
> > > +
> > > +	INIT_WORK(&job->cleanup_work, cleanup_work);
> > > +	queue_work(system_long_wq, &job->cleanup_work);
> > > +}
> > > +EXPORT_SYMBOL(drm_writeback_signal_completion);
> > > diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
> > > index cf13842a6dbd..d7b0263cc5cf 100644
> > > --- a/include/drm/drm_atomic.h
> > > +++ b/include/drm/drm_atomic.h
> > > @@ -594,6 +594,9 @@ void drm_atomic_set_fence_for_plane(struct drm_plane_state *plane_state,
> > >  int __must_check
> > >  drm_atomic_set_crtc_for_connector(struct drm_connector_state *conn_state,
> > >  				  struct drm_crtc *crtc);
> > > +int drm_atomic_set_writeback_fb_for_connector(
> > > +		struct drm_connector_state *conn_state,
> > > +		struct drm_framebuffer *fb);
> > >  int __must_check
> > >  drm_atomic_add_affected_connectors(struct drm_atomic_state *state,
> > >  				   struct drm_crtc *crtc);
> > > diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
> > > index 758a176e7b57..8701ebcc68b3 100644
> > > --- a/include/drm/drm_connector.h
> > > +++ b/include/drm/drm_connector.h
> > > @@ -425,6 +425,19 @@ struct drm_connector_state {
> > >  	 * protection. This is most commonly used for HDCP.
> > >  	 */
> > >  	unsigned int content_protection;
> > > +
> > > +	/**
> > > +	 * @writeback_job: Writeback job for writeback connectors
> > > +	 *
> > > +	 * Holds the framebuffer for a writeback connector. As the writeback
> > > +	 * completion may be asynchronous to the normal commit cycle, the
> > > +	 * writeback job lifetime is managed separately from the normal atomic
> > > +	 * state by this object.
> > > +	 *
> > > +	 * See also: drm_writeback_queue_job() and
> > > +	 * drm_writeback_signal_completion()
> > > +	 */
> > > +	struct drm_writeback_job *writeback_job;
> > >  };
> > >  
> > >  /**
> > > diff --git a/include/drm/drm_mode_config.h b/include/drm/drm_mode_config.h
> > > index 7569f22ffef6..c012e1148ec0 100644
> > > --- a/include/drm/drm_mode_config.h
> > > +++ b/include/drm/drm_mode_config.h
> > > @@ -779,6 +779,20 @@ struct drm_mode_config {
> > >  	 */
> > >  	struct drm_property *panel_orientation_property;
> > >  
> > > +	/**
> > > +	 * @writeback_fb_id_property: Property for writeback connectors, storing
> > > +	 * the ID of the output framebuffer.
> > > +	 * See also: drm_writeback_connector_init()
> > > +	 */
> > > +	struct drm_property *writeback_fb_id_property;
> > > +	/**
> > > +	 * @writeback_pixel_formats_property: Property for writeback connectors,
> > > +	 * storing an array of the supported pixel formats for the writeback
> > > +	 * engine (read-only).
> > > +	 * See also: drm_writeback_connector_init()
> > > +	 */
> > > +	struct drm_property *writeback_pixel_formats_property;
> > > +
> > >  	/* dumb ioctl parameters */
> > >  	uint32_t preferred_depth, prefer_shadow;
> > >  
> > > diff --git a/include/drm/drm_modeset_helper_vtables.h b/include/drm/drm_modeset_helper_vtables.h
> > > index 3e76ca805b0f..97d3a810bc85 100644
> > > --- a/include/drm/drm_modeset_helper_vtables.h
> > > +++ b/include/drm/drm_modeset_helper_vtables.h
> > > @@ -974,6 +974,17 @@ struct drm_connector_helper_funcs {
> > >  	 */
> > >  	int (*atomic_check)(struct drm_connector *connector,
> > >  			    struct drm_connector_state *state);
> > > +
> > > +	/**
> > > +	 * @atomic_commit:
> > > +	 *
> > > +	 * For write-back connectors, this is the point to commit the write-
> > > +	 * back job to hw.
> > > +	 *
> > > +	 * This callback is used by the atomic modeset helpers.
> > > +	 */
> > > +	void (*atomic_commit)(struct drm_connector *connector,
> > > +			      struct drm_writeback_job *writeback_job);
> > >  };
> > >  
> > >  /**
> > > diff --git a/include/drm/drm_writeback.h b/include/drm/drm_writeback.h
> > > new file mode 100644
> > > index 000000000000..0bb95fd4907d
> > > --- /dev/null
> > > +++ b/include/drm/drm_writeback.h
> > > @@ -0,0 +1,89 @@
> > > +/*
> > > + * (C) COPYRIGHT 2016 ARM Limited. All rights reserved.
> > > + * Author: Brian Starkey <brian.starkey@arm.com>
> > > + *
> > > + * This program is free software and is provided to you under the terms of the
> > > + * GNU General Public License version 2 as published by the Free Software
> > > + * Foundation, and any use by you of this program is subject to the terms
> > > + * of such GNU licence.
> > > + */
> > > +
> > > +#ifndef __DRM_WRITEBACK_H__
> > > +#define __DRM_WRITEBACK_H__
> > > +#include <drm/drm_connector.h>
> > > +#include <drm/drm_encoder.h>
> > > +#include <linux/workqueue.h>
> > > +
> > > +struct drm_writeback_connector {
> > > +	struct drm_connector base;
> > > +
> > > +	/**
> > > +	 * @encoder: Internal encoder used by the connector to fulfill
> > > +	 * the DRM framework requirements. The users of the
> > > +	 * @drm_writeback_connector control the behaviour of the @encoder
> > > +	 * by passing the @enc_funcs parameter to drm_writeback_connector_init()
> > > +	 * function.
> > > +	 */
> > > +	struct drm_encoder encoder;
> > > +
> > > +	/**
> > > +	 * @pixel_formats_blob_ptr:
> > > +	 *
> > > +	 * DRM blob property data for the pixel formats list on writeback
> > > +	 * connectors
> > > +	 * See also drm_writeback_connector_init()
> > > +	 */
> > > +	struct drm_property_blob *pixel_formats_blob_ptr;
> > > +
> > > +	/** @job_lock: Protects job_queue */
> > > +	spinlock_t job_lock;
> > > +
> > > +	/**
> > > +	 * @job_queue:
> > > +	 *
> > > +	 * Holds a list of a connector's writeback jobs; the last item is the
> > > +	 * most recent. The first item may be either waiting for the hardware
> > > +	 * to begin writing, or currently being written.
> > > +	 *
> > > +	 * See also: drm_writeback_queue_job() and
> > > +	 * drm_writeback_signal_completion()
> > > +	 */
> > > +	struct list_head job_queue;
> > > +};
> > > +#define to_wb_connector(x) container_of(x, struct drm_writeback_connector, base)
> > > +
> > > +struct drm_writeback_job {
> > > +	/**
> > > +	 * @cleanup_work:
> > > +	 *
> > > +	 * Used to allow drm_writeback_signal_completion to defer dropping the
> > > +	 * framebuffer reference to a workqueue.
> > > +	 */
> > > +	struct work_struct cleanup_work;
> > > +	/**
> > > +	 * @list_entry:
> > > +	 *
> > > +	 * List item for the connector's @job_queue
> > > +	 */
> > > +	struct list_head list_entry;
> > > +	/**
> > > +	 * @fb:
> > > +	 *
> > > +	 * Framebuffer to be written to by the writeback connector. Do not set
> > > +	 * directly, use drm_atomic_set_writeback_fb_for_connector()
> > > +	 */
> > > +	struct drm_framebuffer *fb;
> > > +};
> > > +
> > > +int drm_writeback_connector_init(struct drm_device *dev,
> > > +				 struct drm_writeback_connector *wb_connector,
> > > +				 const struct drm_connector_funcs *con_funcs,
> > > +				 const struct drm_encoder_helper_funcs *enc_helper_funcs,
> > > +				 const u32 *formats, int n_formats);
> > > +
> > > +void drm_writeback_queue_job(struct drm_writeback_connector *wb_connector,
> > > +			     struct drm_writeback_job *job);
> > > +
> > > +void drm_writeback_cleanup_job(struct drm_writeback_job *job);
> > > +void drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector);
> > > +#endif
> > > diff --git a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h
> > > index 2c575794fb52..7b47e184e95e 100644
> > > --- a/include/uapi/drm/drm_mode.h
> > > +++ b/include/uapi/drm/drm_mode.h
> > > @@ -338,6 +338,7 @@ enum drm_mode_subconnector {
> > >  #define DRM_MODE_CONNECTOR_VIRTUAL      15
> > >  #define DRM_MODE_CONNECTOR_DSI		16
> > >  #define DRM_MODE_CONNECTOR_DPI		17
> > > +#define DRM_MODE_CONNECTOR_WRITEBACK	18
> > >  
> > >  struct drm_mode_get_connector {
> > >  
> > > -- 
> > > 2.14.3
> > > 
> > 
> > -- 
> > Sean Paul, Software Engineer, Google / Chromium OS
> 
> -- 
> ====================
> | I would like to |
> | fix the world,  |
> | but they're not |
> | giving me the   |
>  \ source code!  /
>   ---------------
>     ¯\_(ツ)_/¯

-- 
Sean Paul, Software Engineer, Google / Chromium OS
_______________________________________________
Freedreno mailing list
Freedreno@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/freedreno

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [RFC 1/4] drm: Add writeback connector type
@ 2018-02-23 16:39               ` Sean Paul
  0 siblings, 0 replies; 41+ messages in thread
From: Sean Paul @ 2018-02-23 16:39 UTC (permalink / raw)
  To: Liviu Dudau
  Cc: Sean Paul, Rob Clark, dri-devel, freedreno, linux-arm-msm,
	Brian Starkey, Mihail Atanassov, Gustavo Padovan,
	Maarten Lankhorst, David Airlie, Jonathan Corbet, linux-doc,
	linux-kernel

On Fri, Feb 23, 2018 at 04:21:05PM +0000, Liviu Dudau wrote:
> On Fri, Feb 23, 2018 at 10:59:35AM -0500, Sean Paul wrote:
> > On Fri, Feb 23, 2018 at 08:17:51AM -0500, Rob Clark wrote:
> > > From: Brian Starkey <brian.starkey@arm.com>
> > > 
> > > Writeback connectors represent writeback engines which can write the
> > > CRTC output to a memory framebuffer. Add a writeback connector type and
> > > related support functions.
> > > 
> > > Drivers should initialize a writeback connector with
> > > drm_writeback_connector_init() which takes care of setting up all the
> > > writeback-specific details on top of the normal functionality of
> > > drm_connector_init().
> > > 
> > > Writeback connectors have a WRITEBACK_FB_ID property, used to set the
> > > output framebuffer, and a WRITEBACK_PIXEL_FORMATS blob used to expose the
> > > supported writeback formats to userspace.
> > > 
> > > When a framebuffer is attached to a writeback connector with the
> > > WRITEBACK_FB_ID property, it is used only once (for the commit in which
> > > it was included), and userspace can never read back the value of
> > > WRITEBACK_FB_ID. WRITEBACK_FB_ID can only be set if the connector is
> > > attached to a CRTC.
> > > 
> > > Changes since v1:
> > >  - Added drm_writeback.c + documentation
> > >  - Added helper to initialize writeback connector in one go
> > >  - Added core checks
> > >  - Squashed into a single commit
> > >  - Dropped the client cap
> > >  - Writeback framebuffers are no longer persistent
> > > 
> > > Changes since v2:
> > >  Daniel Vetter:
> > >  - Subclass drm_connector to drm_writeback_connector
> > >  - Relax check to allow CRTC to be set without an FB
> > >  - Add some writeback_ prefixes
> > >  - Drop PIXEL_FORMATS_SIZE property, as it was unnecessary
> > >  Gustavo Padovan:
> > >  - Add drm_writeback_job to handle writeback signalling centrally
> > > 
> > > Changes since v3:
> > >  - Rebased
> > >  - Rename PIXEL_FORMATS -> WRITEBACK_PIXEL_FORMATS
> > > 
> > > Changes since v4:
> > >  - Added atomic_commit() vfunc to connector helper funcs, so that
> > >    writeback jobs are committed from atomic helpers
> > > 
> > > Signed-off-by: Brian Starkey <brian.starkey@arm.com>
> > > [rebased and fixed conflicts]
> > > Signed-off-by: Mihail Atanassov <mihail.atanassov@arm.com>
> > > Signed-off-by: Liviu Dudau <liviu.dudau@arm.com>
> > > [rebased and added atomic_commit() vfunc for writeback jobs]
> > > Signed-off-by: Rob Clark <robdclark@gmail.com>
> > > ---
> > >  Documentation/gpu/drm-kms.rst            |   9 ++
> > >  drivers/gpu/drm/Makefile                 |   2 +-
> > >  drivers/gpu/drm/drm_atomic.c             | 130 ++++++++++++++++
> > >  drivers/gpu/drm/drm_atomic_helper.c      |  30 ++++
> > >  drivers/gpu/drm/drm_connector.c          |   4 +-
> > >  drivers/gpu/drm/drm_writeback.c          | 257 +++++++++++++++++++++++++++++++
> > >  include/drm/drm_atomic.h                 |   3 +
> > >  include/drm/drm_connector.h              |  13 ++
> > >  include/drm/drm_mode_config.h            |  14 ++
> > >  include/drm/drm_modeset_helper_vtables.h |  11 ++
> > >  include/drm/drm_writeback.h              |  89 +++++++++++
> > >  include/uapi/drm/drm_mode.h              |   1 +
> > >  12 files changed, 561 insertions(+), 2 deletions(-)
> > >  create mode 100644 drivers/gpu/drm/drm_writeback.c
> > >  create mode 100644 include/drm/drm_writeback.h
> > > 
> > > diff --git a/Documentation/gpu/drm-kms.rst b/Documentation/gpu/drm-kms.rst
> > > index 2dcf5b42015d..e7590dd2f71e 100644
> > > --- a/Documentation/gpu/drm-kms.rst
> > > +++ b/Documentation/gpu/drm-kms.rst
> > > @@ -370,6 +370,15 @@ Connector Functions Reference
> > >  .. kernel-doc:: drivers/gpu/drm/drm_connector.c
> > >     :export:
> > >  
> > > +Writeback Connectors
> > > +--------------------
> > > +
> > > +.. kernel-doc:: drivers/gpu/drm/drm_writeback.c
> > > +  :doc: overview
> > > +
> > > +.. kernel-doc:: drivers/gpu/drm/drm_writeback.c
> > > +  :export:
> > > +
> > >  Encoder Abstraction
> > >  ===================
> > >  
> > > diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> > > index 50093ff4479b..3d708959b224 100644
> > > --- a/drivers/gpu/drm/Makefile
> > > +++ b/drivers/gpu/drm/Makefile
> > > @@ -18,7 +18,7 @@ drm-y       :=	drm_auth.o drm_bufs.o drm_cache.o \
> > >  		drm_encoder.o drm_mode_object.o drm_property.o \
> > >  		drm_plane.o drm_color_mgmt.o drm_print.o \
> > >  		drm_dumb_buffers.o drm_mode_config.o drm_vblank.o \
> > > -		drm_syncobj.o drm_lease.o
> > > +		drm_syncobj.o drm_lease.o drm_writeback.o
> > >  
> > >  drm-$(CONFIG_DRM_LIB_RANDOM) += lib/drm_random.o
> > >  drm-$(CONFIG_DRM_VM) += drm_vm.o
> > > diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
> > > index 46733d534587..019f131fe8be 100644
> > > --- a/drivers/gpu/drm/drm_atomic.c
> > > +++ b/drivers/gpu/drm/drm_atomic.c
> > > @@ -30,6 +30,7 @@
> > >  #include <drm/drm_atomic.h>
> > >  #include <drm/drm_mode.h>
> > >  #include <drm/drm_print.h>
> > > +#include <drm/drm_writeback.h>
> > >  #include <linux/sync_file.h>
> > >  
> > >  #include "drm_crtc_internal.h"
> > > @@ -638,6 +639,46 @@ static void drm_atomic_crtc_print_state(struct drm_printer *p,
> > >  		crtc->funcs->atomic_print_state(p, state);
> > >  }
> > >  
> > > +/**
> > > + * drm_atomic_connector_check - check connector state
> > > + * @connector: connector to check
> > > + * @state: connector state to check
> > > + *
> > > + * Provides core sanity checks for connector state.
> > > + *
> > > + * RETURNS:
> > > + * Zero on success, error code on failure
> > > + */
> > > +static int drm_atomic_connector_check(struct drm_connector *connector,
> > > +		struct drm_connector_state *state)
> > > +{
> > > +	struct drm_crtc_state *crtc_state;
> > > +	struct drm_writeback_job *writeback_job = state->writeback_job;
> > > +
> > > +	if ((connector->connector_type != DRM_MODE_CONNECTOR_WRITEBACK) ||
> > > +	    !writeback_job)
> > > +		return 0;
> > > +
> > > +	if (writeback_job->fb && !state->crtc) {
> > > +		DRM_DEBUG_ATOMIC("[CONNECTOR:%d:%s] framebuffer without CRTC\n",
> > > +				 connector->base.id, connector->name);
> > > +		return -EINVAL;
> > > +	}
> > > +
> > > +	if (state->crtc)
> > > +		crtc_state = drm_atomic_get_existing_crtc_state(state->state,
> > > +								state->crtc);
> > > +
> > > +	if (writeback_job->fb && !crtc_state->active) {
> > 
> > Checking for writeback_job->fb is redundant here and above since it's checked
> > first thing as early exit criteria.
> 
> I somehow don't see that check. We check if (!writebac_job) and then all
> other checks are logic ands with other conditions, so I think the check
> here is still relevant.

Ah, yeah, the first check doesn't check for ->fb (I imagined it on the end of
writeback_job. At any rate, if you add a check for !->fb in the first conditional
you don't need it here or in the second check.

> 
> > 
> > > +		DRM_DEBUG_ATOMIC("[CONNECTOR:%d:%s] has framebuffer, but [CRTC:%d] is off\n",
> > > +				 connector->base.id, connector->name,
> > > +				 state->crtc->base.id);
> > > +		return -EINVAL;
> > > +	}
> > > +
> > > +	return 0;
> > > +}
> > > +
> > >  /**
> > >   * drm_atomic_get_plane_state - get plane state
> > >   * @state: global atomic state object
> > > @@ -1230,6 +1271,12 @@ static int drm_atomic_connector_set_property(struct drm_connector *connector,
> > >  			return -EINVAL;
> > >  		}
> > >  		state->content_protection = val;
> > > +	} else if (property == config->writeback_fb_id_property) {
> > > +		struct drm_framebuffer *fb = drm_framebuffer_lookup(dev, NULL, val);
> > > +		int ret = drm_atomic_set_writeback_fb_for_connector(state, fb);
> > > +		if (fb)
> > > +			drm_framebuffer_unreference(fb);
> > > +		return ret;
> > >  	} else if (connector->funcs->atomic_set_property) {
> > >  		return connector->funcs->atomic_set_property(connector,
> > >  				state, property, val);
> > > @@ -1311,6 +1358,9 @@ drm_atomic_connector_get_property(struct drm_connector *connector,
> > >  		*val = state->scaling_mode;
> > >  	} else if (property == connector->content_protection_property) {
> > >  		*val = state->content_protection;
> > > +	} else if (property == config->writeback_fb_id_property) {
> > > +		/* Writeback framebuffer is one-shot, write and forget */
> > > +		*val = 0;
> > >  	} else if (connector->funcs->atomic_get_property) {
> > >  		return connector->funcs->atomic_get_property(connector,
> > >  				state, property, val);
> > > @@ -1518,6 +1568,75 @@ drm_atomic_set_crtc_for_connector(struct drm_connector_state *conn_state,
> > >  }
> > >  EXPORT_SYMBOL(drm_atomic_set_crtc_for_connector);
> > >  
> > > +/*
> > > + * drm_atomic_get_writeback_job - return or allocate a writeback job
> > > + * @conn_state: Connector state to get the job for
> > > + *
> > > + * Writeback jobs have a different lifetime to the atomic state they are
> > > + * associated with. This convenience function takes care of allocating a job
> > > + * if there isn't yet one associated with the connector state, otherwise
> > > + * it just returns the existing job.
> > > + *
> > > + * Returns: The writeback job for the given connector state
> > > + */
> > > +static struct drm_writeback_job *
> > > +drm_atomic_get_writeback_job(struct drm_connector_state *conn_state)
> > > +{
> > > +	WARN_ON(conn_state->connector->connector_type !=
> > > +		DRM_MODE_CONNECTOR_WRITEBACK);
> > > +
> > > +	if (!conn_state->writeback_job)
> > > +		conn_state->writeback_job =
> > > +			kzalloc(sizeof(*conn_state->writeback_job), GFP_KERNEL);
> > > +
> > > +	return conn_state->writeback_job;
> > > +}
> > > +
> > > +/**
> > > + * drm_atomic_set_writeback_fb_for_connector - set writeback framebuffer
> > > + * @conn_state: atomic state object for the connector
> > > + * @fb: fb to use for the connector
> > > + *
> > > + * This is used to set the framebuffer for a writeback connector, which outputs
> > > + * to a buffer instead of an actual physical connector.
> > > + * Changing the assigned framebuffer requires us to grab a reference to the new
> > > + * fb and drop the reference to the old fb, if there is one. This function
> > > + * takes care of all these details besides updating the pointer in the
> > > + * state object itself.
> > > + *
> > > + * Note: The only way conn_state can already have an fb set is if the commit
> > > + * sets the property more than once.
> > > + *
> > > + * See also: drm_writeback_connector_init()
> > > + *
> > > + * Returns: 0 on success
> > > + */
> > > +int drm_atomic_set_writeback_fb_for_connector(
> > > +		struct drm_connector_state *conn_state,
> > > +		struct drm_framebuffer *fb)
> > > +{
> > > +	struct drm_writeback_job *job =
> > > +		drm_atomic_get_writeback_job(conn_state);
> > > +	if (!job)
> > > +		return -ENOMEM;
> > > +
> > > +	if (job->fb)
> > > +		drm_framebuffer_unreference(job->fb);
> > > +	if (fb)
> > > +		drm_framebuffer_reference(fb);
> > > +	job->fb = fb;
> > > +
> > > +	if (fb)
> > > +		DRM_DEBUG_ATOMIC("Set [FB:%d] for connector state %p\n",
> > > +				 fb->base.id, conn_state);
> > > +	else
> > > +		DRM_DEBUG_ATOMIC("Set [NOFB] for connector state %p\n",
> > > +				 conn_state);
> > > +
> > > +	return 0;
> > > +}
> > > +EXPORT_SYMBOL(drm_atomic_set_writeback_fb_for_connector);
> > > +
> > >  /**
> > >   * drm_atomic_add_affected_connectors - add connectors for crtc
> > >   * @state: atomic state
> > > @@ -1636,6 +1755,8 @@ int drm_atomic_check_only(struct drm_atomic_state *state)
> > >  	struct drm_plane_state *plane_state;
> > >  	struct drm_crtc *crtc;
> > >  	struct drm_crtc_state *crtc_state;
> > > +	struct drm_connector *conn;
> > > +	struct drm_connector_state *conn_state;
> > >  	int i, ret = 0;
> > >  
> > >  	DRM_DEBUG_ATOMIC("checking %p\n", state);
> > > @@ -1658,6 +1779,15 @@ int drm_atomic_check_only(struct drm_atomic_state *state)
> > >  		}
> > >  	}
> > >  
> > > +	for_each_new_connector_in_state(state, conn, conn_state, i) {
> > > +		ret = drm_atomic_connector_check(conn, conn_state);
> > > +		if (ret) {
> > > +			DRM_DEBUG_ATOMIC("[CONNECTOR:%d:%s] atomic core check failed\n",
> > > +					 conn->base.id, conn->name);
> > > +			return ret;
> > > +		}
> > > +	}
> > > +
> > >  	if (config->funcs->atomic_check)
> > >  		ret = config->funcs->atomic_check(state->dev, state);
> > >  
> > > diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c
> > > index ae3cbfe9e01c..12b910755d84 100644
> > > --- a/drivers/gpu/drm/drm_atomic_helper.c
> > > +++ b/drivers/gpu/drm/drm_atomic_helper.c
> > > @@ -30,6 +30,7 @@
> > >  #include <drm/drm_plane_helper.h>
> > >  #include <drm/drm_crtc_helper.h>
> > >  #include <drm/drm_atomic_helper.h>
> > > +#include <drm/drm_writeback.h>
> > >  #include <linux/dma-fence.h>
> > >  
> > >  #include "drm_crtc_helper_internal.h"
> > > @@ -1159,6 +1160,27 @@ void drm_atomic_helper_commit_modeset_disables(struct drm_device *dev,
> > >  }
> > >  EXPORT_SYMBOL(drm_atomic_helper_commit_modeset_disables);
> > >  
> > > +static void commit_writebacks(struct drm_device *dev, struct drm_atomic_state *old_state)
> > > +{
> > > +	struct drm_connector *connector;
> > > +	struct drm_connector_state *new_conn_state;
> > > +	int i;
> > > +
> > > +	for_each_new_connector_in_state(old_state, connector, new_conn_state, i) {
> > > +		const struct drm_connector_helper_funcs *funcs;
> > > +
> > > +		funcs = connector->helper_private;
> > > +
> > > +		if (new_conn_state->writeback_job &&
> > > +		    new_conn_state->writeback_job->fb) {
> > > +			WARN_ON(connector->connector_type !=
> > > +				DRM_MODE_CONNECTOR_WRITEBACK);
> > > +			funcs->atomic_commit(connector,
> > > +					     new_conn_state->writeback_job);
> > > +		}
> > > +	}
> > > +}
> > > +
> > >  /**
> > >   * drm_atomic_helper_commit_modeset_enables - modeset commit to enable outputs
> > >   * @dev: DRM device
> > > @@ -1238,6 +1260,8 @@ void drm_atomic_helper_commit_modeset_enables(struct drm_device *dev,
> > >  
> > >  		drm_bridge_enable(encoder->bridge);
> > >  	}
> > > +
> > > +	commit_writebacks(dev, old_state);
> > >  }
> > >  EXPORT_SYMBOL(drm_atomic_helper_commit_modeset_enables);
> > >  
> > > @@ -3627,6 +3651,9 @@ __drm_atomic_helper_connector_duplicate_state(struct drm_connector *connector,
> > >  	if (state->crtc)
> > >  		drm_connector_get(connector);
> > >  	state->commit = NULL;
> > > +
> > > +	/* Don't copy over a writeback job, they are used only once */
> > > +	state->writeback_job = NULL;
> > >  }
> > >  EXPORT_SYMBOL(__drm_atomic_helper_connector_duplicate_state);
> > >  
> > > @@ -3756,6 +3783,9 @@ __drm_atomic_helper_connector_destroy_state(struct drm_connector_state *state)
> > >  
> > >  	if (state->commit)
> > >  		drm_crtc_commit_put(state->commit);
> > > +
> > > +	if (state->writeback_job)
> > > +		drm_writeback_cleanup_job(state->writeback_job);
> > >  }
> > >  EXPORT_SYMBOL(__drm_atomic_helper_connector_destroy_state);
> > >  
> > > diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c
> > > index 16b9c3810af2..add47f06ae70 100644
> > > --- a/drivers/gpu/drm/drm_connector.c
> > > +++ b/drivers/gpu/drm/drm_connector.c
> > > @@ -87,6 +87,7 @@ static struct drm_conn_prop_enum_list drm_connector_enum_list[] = {
> > >  	{ DRM_MODE_CONNECTOR_VIRTUAL, "Virtual" },
> > >  	{ DRM_MODE_CONNECTOR_DSI, "DSI" },
> > >  	{ DRM_MODE_CONNECTOR_DPI, "DPI" },
> > > +	{ DRM_MODE_CONNECTOR_WRITEBACK, "Writeback" },
> > >  };
> > >  
> > >  void drm_connector_ida_init(void)
> > > @@ -249,7 +250,8 @@ int drm_connector_init(struct drm_device *dev,
> > >  	config->num_connector++;
> > >  	spin_unlock_irq(&config->connector_list_lock);
> > >  
> > > -	if (connector_type != DRM_MODE_CONNECTOR_VIRTUAL)
> > > +	if (connector_type != DRM_MODE_CONNECTOR_VIRTUAL &&
> > > +	    connector_type != DRM_MODE_CONNECTOR_WRITEBACK)
> > >  		drm_object_attach_property(&connector->base,
> > >  					      config->edid_property,
> > >  					      0);
> > > diff --git a/drivers/gpu/drm/drm_writeback.c b/drivers/gpu/drm/drm_writeback.c
> > > new file mode 100644
> > > index 000000000000..da61f929cbc3
> > > --- /dev/null
> > > +++ b/drivers/gpu/drm/drm_writeback.c
> > > @@ -0,0 +1,257 @@
> > > +/*
> > > + * (C) COPYRIGHT 2016 ARM Limited. All rights reserved.
> > > + * Author: Brian Starkey <brian.starkey@arm.com>
> > > + *
> > > + * This program is free software and is provided to you under the terms of the
> > > + * GNU General Public License version 2 as published by the Free Software
> > > + * Foundation, and any use by you of this program is subject to the terms
> > > + * of such GNU licence.
> > > + */
> > > +
> > > +#include <drm/drm_crtc.h>
> > > +#include <drm/drm_modeset_helper_vtables.h>
> > > +#include <drm/drm_property.h>
> > > +#include <drm/drm_writeback.h>
> > > +#include <drm/drmP.h>
> > > +
> > > +/**
> > > + * DOC: overview
> > > + *
> > > + * Writeback connectors are used to expose hardware which can write the output
> > > + * from a CRTC to a memory buffer. They are used and act similarly to other
> > > + * types of connectors, with some important differences:
> > > + *  - Writeback connectors don't provide a way to output visually to the user.
> > > + *  - Writeback connectors should always report as "disconnected" (so that
> > > + *    clients which don't understand them will ignore them).
> > 
> > Have we considered hiding writeback behind a client cap instead?
> 
> Yes, changelog says that the client cap was dropped in v2 based on
> feedback (as far as I remember). 
> 
> > 
> > > + *  - Writeback connectors don't have EDID.
> > > + *
> > > + * A framebuffer may only be attached to a writeback connector when the
> > > + * connector is attached to a CRTC. The WRITEBACK_FB_ID property which sets the
> > > + * framebuffer applies only to a single commit (see below). A framebuffer may
> > > + * not be attached while the CRTC is off.
> > > + *
> > > + * Writeback connectors have some additional properties, which userspace
> > > + * can use to query and control them:
> > > + *
> > > + *  "WRITEBACK_FB_ID":
> > > + *	Write-only object property storing a DRM_MODE_OBJECT_FB: it stores the
> > > + *	framebuffer to be written by the writeback connector. This property is
> > > + *	similar to the FB_ID property on planes, but will always read as zero
> > > + *	and is not preserved across commits.
> > > + *	Userspace must set this property to an output buffer every time it
> > > + *	wishes the buffer to get filled.
> > > + *
> > > + *  "WRITEBACK_PIXEL_FORMATS":
> > > + *	Immutable blob property to store the supported pixel formats table. The
> > > + *	data is an array of u32 DRM_FORMAT_* fourcc values.
> > > + *	Userspace can use this blob to find out what pixel formats are supported
> > > + *	by the connector's writeback engine.
> > > + */
> > > +
> > > +static bool create_writeback_properties(struct drm_device *dev)
> > > +{
> > > +	struct drm_property *prop;
> > > +
> > > +	if (!dev->mode_config.writeback_fb_id_property) {
> > 
> > Somewhat an aside, but we should probably come up with more formal rules on
> > where to stick properties. We have some in drm_connector, which I interpret as
> > "connector-only properties", but we also have a bunch in mode_config.
> > 
> > For optional properties which will generally only apply to one connector on a
> > system, I'd be inclined to put it in connector. 
> 
> I can move the writeback-related properties into the connector in the
> next series.
> 

Either way works for me, no need to do this if you're happy with it, I was just
idly musing.

> 
> > 
> > > +		prop = drm_property_create_object(dev, DRM_MODE_PROP_ATOMIC,
> > > +						  "WRITEBACK_FB_ID",
> > > +						  DRM_MODE_OBJECT_FB);
> > > +		if (!prop)
> > > +			return false;
> > > +		dev->mode_config.writeback_fb_id_property = prop;
> > > +	}
> > > +
> > > +	if (!dev->mode_config.writeback_pixel_formats_property) {
> > > +		prop = drm_property_create(dev, DRM_MODE_PROP_BLOB | DRM_MODE_PROP_IMMUTABLE,
> > > +					   "WRITEBACK_PIXEL_FORMATS", 0);
> > 
> > Does it make sense to expose the formats if u/s doesn't support atomic and can't
> > use writeback? Perhaps this should also be DRM_MODE_PROP_ATOMIC.
> 
> Agree, probably all the writeback properties should only be available if
> the client selects DRM_MODE_PROP_ATOMIC.
> 
> > 
> > 
> > > +		if (!prop)
> > > +			return false;
> > > +		dev->mode_config.writeback_pixel_formats_property = prop;
> > > +	}
> > > +
> > > +	return true;
> > > +}
> > > +
> > > +static const struct drm_encoder_funcs drm_writeback_encoder_funcs = {
> > > +	.destroy = drm_encoder_cleanup,
> > > +};
> > > +
> > > +/**
> > > + * drm_writeback_connector_init - Initialize a writeback connector and its properties
> > > + * @dev: DRM device
> > > + * @wb_connector: Writeback connector to initialize
> > > + * @con_funcs: Connector funcs vtable
> > > + * @enc_helper_funcs: Encoder helper funcs vtable to be used by the internal encoder
> > > + * @formats: Array of supported pixel formats for the writeback engine
> > > + * @n_formats: Length of the formats array
> > > + *
> > > + * This function creates the writeback-connector-specific properties if they
> > > + * have not been already created, initializes the connector as
> > > + * type DRM_MODE_CONNECTOR_WRITEBACK, and correctly initializes the property
> > > + * values. It will also create an internal encoder associated with the
> > > + * drm_writeback_connector and set it to use the @enc_helper_funcs vtable for
> > > + * the encoder helper.
> > > + *
> > > + * Drivers should always use this function instead of drm_connector_init() to
> > > + * set up writeback connectors.
> > > + *
> > > + * Returns: 0 on success, or a negative error code
> > > + */
> > > +int drm_writeback_connector_init(struct drm_device *dev,
> > > +				 struct drm_writeback_connector *wb_connector,
> > > +				 const struct drm_connector_funcs *con_funcs,
> > > +				 const struct drm_encoder_helper_funcs *enc_helper_funcs,
> > > +				 const u32 *formats, int n_formats)
> > > +{
> > > +	int ret;
> > > +	struct drm_property_blob *blob;
> > > +	struct drm_connector *connector = &wb_connector->base;
> > > +	struct drm_mode_config *config = &dev->mode_config;
> > > +
> > > +	if (!create_writeback_properties(dev))
> > > +		return -EINVAL;
> > > +
> > > +	blob = drm_property_create_blob(dev, n_formats * sizeof(*formats),
> > > +					formats);
> > > +	if (IS_ERR(blob))
> > > +		return PTR_ERR(blob);
> > > +
> > > +	drm_encoder_helper_add(&wb_connector->encoder, enc_helper_funcs);
> > > +	ret = drm_encoder_init(dev, &wb_connector->encoder,
> > > +			       &drm_writeback_encoder_funcs,
> > > +			       DRM_MODE_ENCODER_VIRTUAL, NULL);
> > > +	if (ret)
> > > +		goto fail;
> > > +
> > > +	connector->interlace_allowed = 0;
> > > +
> > > +	ret = drm_connector_init(dev, connector, con_funcs,
> > > +				 DRM_MODE_CONNECTOR_WRITEBACK);
> > > +	if (ret)
> > > +		goto connector_fail;
> > > +
> > > +	ret = drm_mode_connector_attach_encoder(connector,
> > > +						&wb_connector->encoder);
> > > +	if (ret)
> > > +		goto attach_fail;
> > > +
> > > +	INIT_LIST_HEAD(&wb_connector->job_queue);
> > > +	spin_lock_init(&wb_connector->job_lock);
> > > +
> > > +	drm_object_attach_property(&connector->base,
> > > +				   config->writeback_fb_id_property, 0);
> > > +
> > > +	drm_object_attach_property(&connector->base,
> > > +				   config->writeback_pixel_formats_property,
> > > +				   blob->base.id);
> > > +	wb_connector->pixel_formats_blob_ptr = blob;
> > > +
> > > +	return 0;
> > > +
> > > +attach_fail:
> > > +	drm_connector_cleanup(connector);
> > > +connector_fail:
> > > +	drm_encoder_cleanup(&wb_connector->encoder);
> > > +fail:
> > > +	drm_property_unreference_blob(blob);
> > > +	return ret;
> > > +}
> > > +EXPORT_SYMBOL(drm_writeback_connector_init);
> > > +
> > > +/**
> > > + * drm_writeback_queue_job - Queue a writeback job for later signalling
> > > + * @wb_connector: The writeback connector to queue a job on
> > > + * @job: The job to queue
> > > + *
> > > + * This function adds a job to the job_queue for a writeback connector. It
> > > + * should be considered to take ownership of the writeback job, and so any other
> > > + * references to the job must be cleared after calling this function.
> > > + *
> > > + * Drivers must ensure that for a given writeback connector, jobs are queued in
> > > + * exactly the same order as they will be completed by the hardware (and
> > > + * signaled via drm_writeback_signal_completion).
> > > + *
> > > + * For every call to drm_writeback_queue_job() there must be exactly one call to
> > > + * drm_writeback_signal_completion()
> > > + *
> > > + * See also: drm_writeback_signal_completion()
> > > + */
> > > +void drm_writeback_queue_job(struct drm_writeback_connector *wb_connector,
> > > +			     struct drm_writeback_job *job)
> > > +{
> > > +	unsigned long flags;
> > > +
> > > +	spin_lock_irqsave(&wb_connector->job_lock, flags);
> > > +	list_add_tail(&job->list_entry, &wb_connector->job_queue);
> > > +	spin_unlock_irqrestore(&wb_connector->job_lock, flags);
> > > +}
> > > +EXPORT_SYMBOL(drm_writeback_queue_job);
> > > +
> > > +/**
> > > + * drm_writeback_cleanup_job - Cleanup and free a writeback job
> > > + * @job: The writeback job to free
> > > + *
> > > + * Drops any references held by the writeback job, and frees the structure.
> > > + */
> > > +void drm_writeback_cleanup_job(struct drm_writeback_job *job)
> > > +{
> > > +	if (!job)
> > > +		return;
> > > +
> > > +	if (job->fb)
> > > +		drm_framebuffer_unreference(job->fb);
> > > +	kfree(job);
> > > +}
> > > +EXPORT_SYMBOL(drm_writeback_cleanup_job);
> > > +
> > > +/*
> > > + * @cleanup_work: deferred cleanup of a writeback job
> > > + *
> > > + * The job cannot be cleaned up directly in drm_writeback_signal_completion,
> > > + * because it may be called in interrupt context. Dropping the framebuffer
> > > + * reference can sleep, and so the cleanup is deferred to a workqueue.
> > > + */
> > > +static void cleanup_work(struct work_struct *work)
> > > +{
> > > +	struct drm_writeback_job *job = container_of(work,
> > > +						     struct drm_writeback_job,
> > > +						     cleanup_work);
> > > +	drm_writeback_cleanup_job(job);
> > 
> > TIL that you can free work while using it.
> 
> Was not aware that it is possible. I can update the code to do it.

That's what's happening now, afaict. drm_writeback_cleanup_job() frees job, which
contains work in this function.

Sean

> 
> Thanks for the feedback and review!
> 
> Best regards,
> Liviu
> 
> > 
> > > +}
> > > +
> > > +/**
> > > + * drm_writeback_signal_completion - Signal the completion of a writeback job
> > > + * @wb_connector: The writeback connector whose job is complete
> > > + *
> > > + * Drivers should call this to signal the completion of a previously queued
> > > + * writeback job. It should be called as soon as possible after the hardware
> > > + * has finished writing, and may be called from interrupt context.
> > > + * It is the driver's responsibility to ensure that for a given connector, the
> > > + * hardware completes writeback jobs in the same order as they are queued.
> > > + *
> > > + * Unless the driver is holding its own reference to the framebuffer, it must
> > > + * not be accessed after calling this function.
> > > + *
> > > + * See also: drm_writeback_queue_job()
> > > + */
> > > +void
> > > +drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector)
> > > +{
> > > +	unsigned long flags;
> > > +	struct drm_writeback_job *job;
> > > +
> > > +	spin_lock_irqsave(&wb_connector->job_lock, flags);
> > > +	job = list_first_entry_or_null(&wb_connector->job_queue,
> > > +				       struct drm_writeback_job,
> > > +				       list_entry);
> > > +	if (job)
> > > +		list_del(&job->list_entry);
> > > +	spin_unlock_irqrestore(&wb_connector->job_lock, flags);
> > > +
> > > +	if (WARN_ON(!job))
> > > +		return;
> > > +
> > > +	INIT_WORK(&job->cleanup_work, cleanup_work);
> > > +	queue_work(system_long_wq, &job->cleanup_work);
> > > +}
> > > +EXPORT_SYMBOL(drm_writeback_signal_completion);
> > > diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
> > > index cf13842a6dbd..d7b0263cc5cf 100644
> > > --- a/include/drm/drm_atomic.h
> > > +++ b/include/drm/drm_atomic.h
> > > @@ -594,6 +594,9 @@ void drm_atomic_set_fence_for_plane(struct drm_plane_state *plane_state,
> > >  int __must_check
> > >  drm_atomic_set_crtc_for_connector(struct drm_connector_state *conn_state,
> > >  				  struct drm_crtc *crtc);
> > > +int drm_atomic_set_writeback_fb_for_connector(
> > > +		struct drm_connector_state *conn_state,
> > > +		struct drm_framebuffer *fb);
> > >  int __must_check
> > >  drm_atomic_add_affected_connectors(struct drm_atomic_state *state,
> > >  				   struct drm_crtc *crtc);
> > > diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
> > > index 758a176e7b57..8701ebcc68b3 100644
> > > --- a/include/drm/drm_connector.h
> > > +++ b/include/drm/drm_connector.h
> > > @@ -425,6 +425,19 @@ struct drm_connector_state {
> > >  	 * protection. This is most commonly used for HDCP.
> > >  	 */
> > >  	unsigned int content_protection;
> > > +
> > > +	/**
> > > +	 * @writeback_job: Writeback job for writeback connectors
> > > +	 *
> > > +	 * Holds the framebuffer for a writeback connector. As the writeback
> > > +	 * completion may be asynchronous to the normal commit cycle, the
> > > +	 * writeback job lifetime is managed separately from the normal atomic
> > > +	 * state by this object.
> > > +	 *
> > > +	 * See also: drm_writeback_queue_job() and
> > > +	 * drm_writeback_signal_completion()
> > > +	 */
> > > +	struct drm_writeback_job *writeback_job;
> > >  };
> > >  
> > >  /**
> > > diff --git a/include/drm/drm_mode_config.h b/include/drm/drm_mode_config.h
> > > index 7569f22ffef6..c012e1148ec0 100644
> > > --- a/include/drm/drm_mode_config.h
> > > +++ b/include/drm/drm_mode_config.h
> > > @@ -779,6 +779,20 @@ struct drm_mode_config {
> > >  	 */
> > >  	struct drm_property *panel_orientation_property;
> > >  
> > > +	/**
> > > +	 * @writeback_fb_id_property: Property for writeback connectors, storing
> > > +	 * the ID of the output framebuffer.
> > > +	 * See also: drm_writeback_connector_init()
> > > +	 */
> > > +	struct drm_property *writeback_fb_id_property;
> > > +	/**
> > > +	 * @writeback_pixel_formats_property: Property for writeback connectors,
> > > +	 * storing an array of the supported pixel formats for the writeback
> > > +	 * engine (read-only).
> > > +	 * See also: drm_writeback_connector_init()
> > > +	 */
> > > +	struct drm_property *writeback_pixel_formats_property;
> > > +
> > >  	/* dumb ioctl parameters */
> > >  	uint32_t preferred_depth, prefer_shadow;
> > >  
> > > diff --git a/include/drm/drm_modeset_helper_vtables.h b/include/drm/drm_modeset_helper_vtables.h
> > > index 3e76ca805b0f..97d3a810bc85 100644
> > > --- a/include/drm/drm_modeset_helper_vtables.h
> > > +++ b/include/drm/drm_modeset_helper_vtables.h
> > > @@ -974,6 +974,17 @@ struct drm_connector_helper_funcs {
> > >  	 */
> > >  	int (*atomic_check)(struct drm_connector *connector,
> > >  			    struct drm_connector_state *state);
> > > +
> > > +	/**
> > > +	 * @atomic_commit:
> > > +	 *
> > > +	 * For write-back connectors, this is the point to commit the write-
> > > +	 * back job to hw.
> > > +	 *
> > > +	 * This callback is used by the atomic modeset helpers.
> > > +	 */
> > > +	void (*atomic_commit)(struct drm_connector *connector,
> > > +			      struct drm_writeback_job *writeback_job);
> > >  };
> > >  
> > >  /**
> > > diff --git a/include/drm/drm_writeback.h b/include/drm/drm_writeback.h
> > > new file mode 100644
> > > index 000000000000..0bb95fd4907d
> > > --- /dev/null
> > > +++ b/include/drm/drm_writeback.h
> > > @@ -0,0 +1,89 @@
> > > +/*
> > > + * (C) COPYRIGHT 2016 ARM Limited. All rights reserved.
> > > + * Author: Brian Starkey <brian.starkey@arm.com>
> > > + *
> > > + * This program is free software and is provided to you under the terms of the
> > > + * GNU General Public License version 2 as published by the Free Software
> > > + * Foundation, and any use by you of this program is subject to the terms
> > > + * of such GNU licence.
> > > + */
> > > +
> > > +#ifndef __DRM_WRITEBACK_H__
> > > +#define __DRM_WRITEBACK_H__
> > > +#include <drm/drm_connector.h>
> > > +#include <drm/drm_encoder.h>
> > > +#include <linux/workqueue.h>
> > > +
> > > +struct drm_writeback_connector {
> > > +	struct drm_connector base;
> > > +
> > > +	/**
> > > +	 * @encoder: Internal encoder used by the connector to fulfill
> > > +	 * the DRM framework requirements. The users of the
> > > +	 * @drm_writeback_connector control the behaviour of the @encoder
> > > +	 * by passing the @enc_funcs parameter to drm_writeback_connector_init()
> > > +	 * function.
> > > +	 */
> > > +	struct drm_encoder encoder;
> > > +
> > > +	/**
> > > +	 * @pixel_formats_blob_ptr:
> > > +	 *
> > > +	 * DRM blob property data for the pixel formats list on writeback
> > > +	 * connectors
> > > +	 * See also drm_writeback_connector_init()
> > > +	 */
> > > +	struct drm_property_blob *pixel_formats_blob_ptr;
> > > +
> > > +	/** @job_lock: Protects job_queue */
> > > +	spinlock_t job_lock;
> > > +
> > > +	/**
> > > +	 * @job_queue:
> > > +	 *
> > > +	 * Holds a list of a connector's writeback jobs; the last item is the
> > > +	 * most recent. The first item may be either waiting for the hardware
> > > +	 * to begin writing, or currently being written.
> > > +	 *
> > > +	 * See also: drm_writeback_queue_job() and
> > > +	 * drm_writeback_signal_completion()
> > > +	 */
> > > +	struct list_head job_queue;
> > > +};
> > > +#define to_wb_connector(x) container_of(x, struct drm_writeback_connector, base)
> > > +
> > > +struct drm_writeback_job {
> > > +	/**
> > > +	 * @cleanup_work:
> > > +	 *
> > > +	 * Used to allow drm_writeback_signal_completion to defer dropping the
> > > +	 * framebuffer reference to a workqueue.
> > > +	 */
> > > +	struct work_struct cleanup_work;
> > > +	/**
> > > +	 * @list_entry:
> > > +	 *
> > > +	 * List item for the connector's @job_queue
> > > +	 */
> > > +	struct list_head list_entry;
> > > +	/**
> > > +	 * @fb:
> > > +	 *
> > > +	 * Framebuffer to be written to by the writeback connector. Do not set
> > > +	 * directly, use drm_atomic_set_writeback_fb_for_connector()
> > > +	 */
> > > +	struct drm_framebuffer *fb;
> > > +};
> > > +
> > > +int drm_writeback_connector_init(struct drm_device *dev,
> > > +				 struct drm_writeback_connector *wb_connector,
> > > +				 const struct drm_connector_funcs *con_funcs,
> > > +				 const struct drm_encoder_helper_funcs *enc_helper_funcs,
> > > +				 const u32 *formats, int n_formats);
> > > +
> > > +void drm_writeback_queue_job(struct drm_writeback_connector *wb_connector,
> > > +			     struct drm_writeback_job *job);
> > > +
> > > +void drm_writeback_cleanup_job(struct drm_writeback_job *job);
> > > +void drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector);
> > > +#endif
> > > diff --git a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h
> > > index 2c575794fb52..7b47e184e95e 100644
> > > --- a/include/uapi/drm/drm_mode.h
> > > +++ b/include/uapi/drm/drm_mode.h
> > > @@ -338,6 +338,7 @@ enum drm_mode_subconnector {
> > >  #define DRM_MODE_CONNECTOR_VIRTUAL      15
> > >  #define DRM_MODE_CONNECTOR_DSI		16
> > >  #define DRM_MODE_CONNECTOR_DPI		17
> > > +#define DRM_MODE_CONNECTOR_WRITEBACK	18
> > >  
> > >  struct drm_mode_get_connector {
> > >  
> > > -- 
> > > 2.14.3
> > > 
> > 
> > -- 
> > Sean Paul, Software Engineer, Google / Chromium OS
> 
> -- 
> ====================
> | I would like to |
> | fix the world,  |
> | but they're not |
> | giving me the   |
>  \ source code!  /
>   ---------------
>     ¯\_(ツ)_/¯

-- 
Sean Paul, Software Engineer, Google / Chromium OS

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [RFC 1/4] drm: Add writeback connector type
  2018-02-23 16:25           ` Rob Clark
@ 2018-02-23 16:43             ` Sean Paul
  -1 siblings, 0 replies; 41+ messages in thread
From: Sean Paul @ 2018-02-23 16:43 UTC (permalink / raw)
  To: Rob Clark
  Cc: Jonathan Corbet, linux-arm-msm, linux-doc, Liviu Dudau,
	Linux Kernel Mailing List, dri-devel, David Airlie,
	Mihail Atanassov, freedreno

On Fri, Feb 23, 2018 at 11:25:11AM -0500, Rob Clark wrote:
> On Fri, Feb 23, 2018 at 10:59 AM, Sean Paul <seanpaul@chromium.org> wrote:
> >
> > Have we considered hiding writeback behind a client cap instead?
> 
> It is kinda *almost* unneeded, since the connector reports itself as
> disconnected.
> 
> I'm not sure what the reason was to drop the cap, but I think it would
> be better to have a cap so WB connectors don't show up in, for ex,
> xrandr

Yeah, the disconnected hack is kind of gross, IMO. I hate to introduce churn in
the patch series given that it was initially introduced with the client cap.

There are also cases where we might want to make writeback unavailable, such as
when content protection is enabled. In those cases, it's conceivable that we
might want to use disconnected as a signal to u/s. I suppose we could also just
fail the check, so most of this is just academic.

Sean


> 
> BR,
> -R

-- 
Sean Paul, Software Engineer, Google / Chromium OS
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [RFC 1/4] drm: Add writeback connector type
@ 2018-02-23 16:43             ` Sean Paul
  0 siblings, 0 replies; 41+ messages in thread
From: Sean Paul @ 2018-02-23 16:43 UTC (permalink / raw)
  To: Rob Clark
  Cc: Sean Paul, dri-devel, freedreno, linux-arm-msm, Brian Starkey,
	Liviu Dudau, Mihail Atanassov, Gustavo Padovan,
	Maarten Lankhorst, David Airlie, Jonathan Corbet, linux-doc,
	Linux Kernel Mailing List

On Fri, Feb 23, 2018 at 11:25:11AM -0500, Rob Clark wrote:
> On Fri, Feb 23, 2018 at 10:59 AM, Sean Paul <seanpaul@chromium.org> wrote:
> >
> > Have we considered hiding writeback behind a client cap instead?
> 
> It is kinda *almost* unneeded, since the connector reports itself as
> disconnected.
> 
> I'm not sure what the reason was to drop the cap, but I think it would
> be better to have a cap so WB connectors don't show up in, for ex,
> xrandr

Yeah, the disconnected hack is kind of gross, IMO. I hate to introduce churn in
the patch series given that it was initially introduced with the client cap.

There are also cases where we might want to make writeback unavailable, such as
when content protection is enabled. In those cases, it's conceivable that we
might want to use disconnected as a signal to u/s. I suppose we could also just
fail the check, so most of this is just academic.

Sean


> 
> BR,
> -R

-- 
Sean Paul, Software Engineer, Google / Chromium OS

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [RFC 1/4] drm: Add writeback connector type
  2018-02-23 16:43             ` Sean Paul
@ 2018-02-23 16:48               ` Liviu Dudau
  -1 siblings, 0 replies; 41+ messages in thread
From: Liviu Dudau @ 2018-02-23 16:48 UTC (permalink / raw)
  To: Sean Paul
  Cc: Jonathan Corbet, David Airlie, linux-arm-msm, linux-doc,
	Linux Kernel Mailing List, dri-devel, Mihail Atanassov,
	freedreno

On Fri, Feb 23, 2018 at 11:43:29AM -0500, Sean Paul wrote:
> On Fri, Feb 23, 2018 at 11:25:11AM -0500, Rob Clark wrote:
> > On Fri, Feb 23, 2018 at 10:59 AM, Sean Paul <seanpaul@chromium.org> wrote:
> > >
> > > Have we considered hiding writeback behind a client cap instead?
> > 
> > It is kinda *almost* unneeded, since the connector reports itself as
> > disconnected.
> > 
> > I'm not sure what the reason was to drop the cap, but I think it would
> > be better to have a cap so WB connectors don't show up in, for ex,
> > xrandr
> 
> Yeah, the disconnected hack is kind of gross, IMO. I hate to introduce churn in
> the patch series given that it was initially introduced with the client cap.

Haha, that's the reverse of Daniel's position:

https://lists.freedesktop.org/archives/dri-devel/2016-October/120519.html

> 
> There are also cases where we might want to make writeback unavailable, such as
> when content protection is enabled. In those cases, it's conceivable that we
> might want to use disconnected as a signal to u/s. I suppose we could also just
> fail the check, so most of this is just academic.

Not sure what other hardware out there does, but on Mali DP's case you
would be outputing the protected content by putting the display
processor in secure mode, which automatically disables writeback for us.
Or to put in another way, you don't need a writeback framebuffer if you
are in non-secure mode as you can get access to the framebuffer used for
the plane anyway.

Best regards,
Liviu

> 
> Sean
> 
> 
> > 
> > BR,
> > -R
> 
> -- 
> Sean Paul, Software Engineer, Google / Chromium OS

-- 
====================
| I would like to |
| fix the world,  |
| but they're not |
| giving me the   |
 \ source code!  /
  ---------------
    ¯\_(ツ)_/¯
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [RFC 1/4] drm: Add writeback connector type
@ 2018-02-23 16:48               ` Liviu Dudau
  0 siblings, 0 replies; 41+ messages in thread
From: Liviu Dudau @ 2018-02-23 16:48 UTC (permalink / raw)
  To: Sean Paul
  Cc: Rob Clark, dri-devel, freedreno, linux-arm-msm, Brian Starkey,
	Mihail Atanassov, Gustavo Padovan, Maarten Lankhorst,
	David Airlie, Jonathan Corbet, linux-doc,
	Linux Kernel Mailing List

On Fri, Feb 23, 2018 at 11:43:29AM -0500, Sean Paul wrote:
> On Fri, Feb 23, 2018 at 11:25:11AM -0500, Rob Clark wrote:
> > On Fri, Feb 23, 2018 at 10:59 AM, Sean Paul <seanpaul@chromium.org> wrote:
> > >
> > > Have we considered hiding writeback behind a client cap instead?
> > 
> > It is kinda *almost* unneeded, since the connector reports itself as
> > disconnected.
> > 
> > I'm not sure what the reason was to drop the cap, but I think it would
> > be better to have a cap so WB connectors don't show up in, for ex,
> > xrandr
> 
> Yeah, the disconnected hack is kind of gross, IMO. I hate to introduce churn in
> the patch series given that it was initially introduced with the client cap.

Haha, that's the reverse of Daniel's position:

https://lists.freedesktop.org/archives/dri-devel/2016-October/120519.html

> 
> There are also cases where we might want to make writeback unavailable, such as
> when content protection is enabled. In those cases, it's conceivable that we
> might want to use disconnected as a signal to u/s. I suppose we could also just
> fail the check, so most of this is just academic.

Not sure what other hardware out there does, but on Mali DP's case you
would be outputing the protected content by putting the display
processor in secure mode, which automatically disables writeback for us.
Or to put in another way, you don't need a writeback framebuffer if you
are in non-secure mode as you can get access to the framebuffer used for
the plane anyway.

Best regards,
Liviu

> 
> Sean
> 
> 
> > 
> > BR,
> > -R
> 
> -- 
> Sean Paul, Software Engineer, Google / Chromium OS

-- 
====================
| I would like to |
| fix the world,  |
| but they're not |
| giving me the   |
 \ source code!  /
  ---------------
    ¯\_(ツ)_/¯

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [RFC 1/4] drm: Add writeback connector type
  2018-02-23 16:39               ` Sean Paul
@ 2018-02-23 16:52                 ` Liviu Dudau
  -1 siblings, 0 replies; 41+ messages in thread
From: Liviu Dudau @ 2018-02-23 16:52 UTC (permalink / raw)
  To: Sean Paul
  Cc: Jonathan Corbet, David Airlie,
	linux-arm-msm-u79uwXL29TY76Z2rM5mHXA,
	linux-doc-u79uwXL29TY76Z2rM5mHXA, Maarten Lankhorst,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, Rob Clark,
	Gustavo Padovan, Mihail Atanassov,
	freedreno-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, Brian Starkey

On Fri, Feb 23, 2018 at 11:39:06AM -0500, Sean Paul wrote:
> On Fri, Feb 23, 2018 at 04:21:05PM +0000, Liviu Dudau wrote:
> > On Fri, Feb 23, 2018 at 10:59:35AM -0500, Sean Paul wrote:
> > > On Fri, Feb 23, 2018 at 08:17:51AM -0500, Rob Clark wrote:
> > > > From: Brian Starkey <brian.starkey@arm.com>
> > > > 
> > > > Writeback connectors represent writeback engines which can write the
> > > > CRTC output to a memory framebuffer. Add a writeback connector type and
> > > > related support functions.
> > > > 
> > > > Drivers should initialize a writeback connector with
> > > > drm_writeback_connector_init() which takes care of setting up all the
> > > > writeback-specific details on top of the normal functionality of
> > > > drm_connector_init().
> > > > 
> > > > Writeback connectors have a WRITEBACK_FB_ID property, used to set the
> > > > output framebuffer, and a WRITEBACK_PIXEL_FORMATS blob used to expose the
> > > > supported writeback formats to userspace.
> > > > 
> > > > When a framebuffer is attached to a writeback connector with the
> > > > WRITEBACK_FB_ID property, it is used only once (for the commit in which
> > > > it was included), and userspace can never read back the value of
> > > > WRITEBACK_FB_ID. WRITEBACK_FB_ID can only be set if the connector is
> > > > attached to a CRTC.
> > > > 
> > > > Changes since v1:
> > > >  - Added drm_writeback.c + documentation
> > > >  - Added helper to initialize writeback connector in one go
> > > >  - Added core checks
> > > >  - Squashed into a single commit
> > > >  - Dropped the client cap
> > > >  - Writeback framebuffers are no longer persistent
> > > > 
> > > > Changes since v2:
> > > >  Daniel Vetter:
> > > >  - Subclass drm_connector to drm_writeback_connector
> > > >  - Relax check to allow CRTC to be set without an FB
> > > >  - Add some writeback_ prefixes
> > > >  - Drop PIXEL_FORMATS_SIZE property, as it was unnecessary
> > > >  Gustavo Padovan:
> > > >  - Add drm_writeback_job to handle writeback signalling centrally
> > > > 
> > > > Changes since v3:
> > > >  - Rebased
> > > >  - Rename PIXEL_FORMATS -> WRITEBACK_PIXEL_FORMATS
> > > > 
> > > > Changes since v4:
> > > >  - Added atomic_commit() vfunc to connector helper funcs, so that
> > > >    writeback jobs are committed from atomic helpers
> > > > 
> > > > Signed-off-by: Brian Starkey <brian.starkey@arm.com>
> > > > [rebased and fixed conflicts]
> > > > Signed-off-by: Mihail Atanassov <mihail.atanassov@arm.com>
> > > > Signed-off-by: Liviu Dudau <liviu.dudau@arm.com>
> > > > [rebased and added atomic_commit() vfunc for writeback jobs]
> > > > Signed-off-by: Rob Clark <robdclark@gmail.com>
> > > > ---
> > > >  Documentation/gpu/drm-kms.rst            |   9 ++
> > > >  drivers/gpu/drm/Makefile                 |   2 +-
> > > >  drivers/gpu/drm/drm_atomic.c             | 130 ++++++++++++++++
> > > >  drivers/gpu/drm/drm_atomic_helper.c      |  30 ++++
> > > >  drivers/gpu/drm/drm_connector.c          |   4 +-
> > > >  drivers/gpu/drm/drm_writeback.c          | 257 +++++++++++++++++++++++++++++++
> > > >  include/drm/drm_atomic.h                 |   3 +
> > > >  include/drm/drm_connector.h              |  13 ++
> > > >  include/drm/drm_mode_config.h            |  14 ++
> > > >  include/drm/drm_modeset_helper_vtables.h |  11 ++
> > > >  include/drm/drm_writeback.h              |  89 +++++++++++
> > > >  include/uapi/drm/drm_mode.h              |   1 +
> > > >  12 files changed, 561 insertions(+), 2 deletions(-)
> > > >  create mode 100644 drivers/gpu/drm/drm_writeback.c
> > > >  create mode 100644 include/drm/drm_writeback.h
> > > > 
> > > > diff --git a/Documentation/gpu/drm-kms.rst b/Documentation/gpu/drm-kms.rst
> > > > index 2dcf5b42015d..e7590dd2f71e 100644
> > > > --- a/Documentation/gpu/drm-kms.rst
> > > > +++ b/Documentation/gpu/drm-kms.rst
> > > > @@ -370,6 +370,15 @@ Connector Functions Reference
> > > >  .. kernel-doc:: drivers/gpu/drm/drm_connector.c
> > > >     :export:
> > > >  
> > > > +Writeback Connectors
> > > > +--------------------
> > > > +
> > > > +.. kernel-doc:: drivers/gpu/drm/drm_writeback.c
> > > > +  :doc: overview
> > > > +
> > > > +.. kernel-doc:: drivers/gpu/drm/drm_writeback.c
> > > > +  :export:
> > > > +
> > > >  Encoder Abstraction
> > > >  ===================
> > > >  
> > > > diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> > > > index 50093ff4479b..3d708959b224 100644
> > > > --- a/drivers/gpu/drm/Makefile
> > > > +++ b/drivers/gpu/drm/Makefile
> > > > @@ -18,7 +18,7 @@ drm-y       :=	drm_auth.o drm_bufs.o drm_cache.o \
> > > >  		drm_encoder.o drm_mode_object.o drm_property.o \
> > > >  		drm_plane.o drm_color_mgmt.o drm_print.o \
> > > >  		drm_dumb_buffers.o drm_mode_config.o drm_vblank.o \
> > > > -		drm_syncobj.o drm_lease.o
> > > > +		drm_syncobj.o drm_lease.o drm_writeback.o
> > > >  
> > > >  drm-$(CONFIG_DRM_LIB_RANDOM) += lib/drm_random.o
> > > >  drm-$(CONFIG_DRM_VM) += drm_vm.o
> > > > diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
> > > > index 46733d534587..019f131fe8be 100644
> > > > --- a/drivers/gpu/drm/drm_atomic.c
> > > > +++ b/drivers/gpu/drm/drm_atomic.c
> > > > @@ -30,6 +30,7 @@
> > > >  #include <drm/drm_atomic.h>
> > > >  #include <drm/drm_mode.h>
> > > >  #include <drm/drm_print.h>
> > > > +#include <drm/drm_writeback.h>
> > > >  #include <linux/sync_file.h>
> > > >  
> > > >  #include "drm_crtc_internal.h"
> > > > @@ -638,6 +639,46 @@ static void drm_atomic_crtc_print_state(struct drm_printer *p,
> > > >  		crtc->funcs->atomic_print_state(p, state);
> > > >  }
> > > >  
> > > > +/**
> > > > + * drm_atomic_connector_check - check connector state
> > > > + * @connector: connector to check
> > > > + * @state: connector state to check
> > > > + *
> > > > + * Provides core sanity checks for connector state.
> > > > + *
> > > > + * RETURNS:
> > > > + * Zero on success, error code on failure
> > > > + */
> > > > +static int drm_atomic_connector_check(struct drm_connector *connector,
> > > > +		struct drm_connector_state *state)
> > > > +{
> > > > +	struct drm_crtc_state *crtc_state;
> > > > +	struct drm_writeback_job *writeback_job = state->writeback_job;
> > > > +
> > > > +	if ((connector->connector_type != DRM_MODE_CONNECTOR_WRITEBACK) ||
> > > > +	    !writeback_job)
> > > > +		return 0;
> > > > +
> > > > +	if (writeback_job->fb && !state->crtc) {
> > > > +		DRM_DEBUG_ATOMIC("[CONNECTOR:%d:%s] framebuffer without CRTC\n",
> > > > +				 connector->base.id, connector->name);
> > > > +		return -EINVAL;
> > > > +	}
> > > > +
> > > > +	if (state->crtc)
> > > > +		crtc_state = drm_atomic_get_existing_crtc_state(state->state,
> > > > +								state->crtc);
> > > > +
> > > > +	if (writeback_job->fb && !crtc_state->active) {
> > > 
> > > Checking for writeback_job->fb is redundant here and above since it's checked
> > > first thing as early exit criteria.
> > 
> > I somehow don't see that check. We check if (!writebac_job) and then all
> > other checks are logic ands with other conditions, so I think the check
> > here is still relevant.
> 
> Ah, yeah, the first check doesn't check for ->fb (I imagined it on the end of
> writeback_job. At any rate, if you add a check for !->fb in the first conditional
> you don't need it here or in the second check.
> 
> > 
> > > 
> > > > +		DRM_DEBUG_ATOMIC("[CONNECTOR:%d:%s] has framebuffer, but [CRTC:%d] is off\n",
> > > > +				 connector->base.id, connector->name,
> > > > +				 state->crtc->base.id);
> > > > +		return -EINVAL;
> > > > +	}
> > > > +
> > > > +	return 0;
> > > > +}
> > > > +
> > > >  /**
> > > >   * drm_atomic_get_plane_state - get plane state
> > > >   * @state: global atomic state object
> > > > @@ -1230,6 +1271,12 @@ static int drm_atomic_connector_set_property(struct drm_connector *connector,
> > > >  			return -EINVAL;
> > > >  		}
> > > >  		state->content_protection = val;
> > > > +	} else if (property == config->writeback_fb_id_property) {
> > > > +		struct drm_framebuffer *fb = drm_framebuffer_lookup(dev, NULL, val);
> > > > +		int ret = drm_atomic_set_writeback_fb_for_connector(state, fb);
> > > > +		if (fb)
> > > > +			drm_framebuffer_unreference(fb);
> > > > +		return ret;
> > > >  	} else if (connector->funcs->atomic_set_property) {
> > > >  		return connector->funcs->atomic_set_property(connector,
> > > >  				state, property, val);
> > > > @@ -1311,6 +1358,9 @@ drm_atomic_connector_get_property(struct drm_connector *connector,
> > > >  		*val = state->scaling_mode;
> > > >  	} else if (property == connector->content_protection_property) {
> > > >  		*val = state->content_protection;
> > > > +	} else if (property == config->writeback_fb_id_property) {
> > > > +		/* Writeback framebuffer is one-shot, write and forget */
> > > > +		*val = 0;
> > > >  	} else if (connector->funcs->atomic_get_property) {
> > > >  		return connector->funcs->atomic_get_property(connector,
> > > >  				state, property, val);
> > > > @@ -1518,6 +1568,75 @@ drm_atomic_set_crtc_for_connector(struct drm_connector_state *conn_state,
> > > >  }
> > > >  EXPORT_SYMBOL(drm_atomic_set_crtc_for_connector);
> > > >  
> > > > +/*
> > > > + * drm_atomic_get_writeback_job - return or allocate a writeback job
> > > > + * @conn_state: Connector state to get the job for
> > > > + *
> > > > + * Writeback jobs have a different lifetime to the atomic state they are
> > > > + * associated with. This convenience function takes care of allocating a job
> > > > + * if there isn't yet one associated with the connector state, otherwise
> > > > + * it just returns the existing job.
> > > > + *
> > > > + * Returns: The writeback job for the given connector state
> > > > + */
> > > > +static struct drm_writeback_job *
> > > > +drm_atomic_get_writeback_job(struct drm_connector_state *conn_state)
> > > > +{
> > > > +	WARN_ON(conn_state->connector->connector_type !=
> > > > +		DRM_MODE_CONNECTOR_WRITEBACK);
> > > > +
> > > > +	if (!conn_state->writeback_job)
> > > > +		conn_state->writeback_job =
> > > > +			kzalloc(sizeof(*conn_state->writeback_job), GFP_KERNEL);
> > > > +
> > > > +	return conn_state->writeback_job;
> > > > +}
> > > > +
> > > > +/**
> > > > + * drm_atomic_set_writeback_fb_for_connector - set writeback framebuffer
> > > > + * @conn_state: atomic state object for the connector
> > > > + * @fb: fb to use for the connector
> > > > + *
> > > > + * This is used to set the framebuffer for a writeback connector, which outputs
> > > > + * to a buffer instead of an actual physical connector.
> > > > + * Changing the assigned framebuffer requires us to grab a reference to the new
> > > > + * fb and drop the reference to the old fb, if there is one. This function
> > > > + * takes care of all these details besides updating the pointer in the
> > > > + * state object itself.
> > > > + *
> > > > + * Note: The only way conn_state can already have an fb set is if the commit
> > > > + * sets the property more than once.
> > > > + *
> > > > + * See also: drm_writeback_connector_init()
> > > > + *
> > > > + * Returns: 0 on success
> > > > + */
> > > > +int drm_atomic_set_writeback_fb_for_connector(
> > > > +		struct drm_connector_state *conn_state,
> > > > +		struct drm_framebuffer *fb)
> > > > +{
> > > > +	struct drm_writeback_job *job =
> > > > +		drm_atomic_get_writeback_job(conn_state);
> > > > +	if (!job)
> > > > +		return -ENOMEM;
> > > > +
> > > > +	if (job->fb)
> > > > +		drm_framebuffer_unreference(job->fb);
> > > > +	if (fb)
> > > > +		drm_framebuffer_reference(fb);
> > > > +	job->fb = fb;
> > > > +
> > > > +	if (fb)
> > > > +		DRM_DEBUG_ATOMIC("Set [FB:%d] for connector state %p\n",
> > > > +				 fb->base.id, conn_state);
> > > > +	else
> > > > +		DRM_DEBUG_ATOMIC("Set [NOFB] for connector state %p\n",
> > > > +				 conn_state);
> > > > +
> > > > +	return 0;
> > > > +}
> > > > +EXPORT_SYMBOL(drm_atomic_set_writeback_fb_for_connector);
> > > > +
> > > >  /**
> > > >   * drm_atomic_add_affected_connectors - add connectors for crtc
> > > >   * @state: atomic state
> > > > @@ -1636,6 +1755,8 @@ int drm_atomic_check_only(struct drm_atomic_state *state)
> > > >  	struct drm_plane_state *plane_state;
> > > >  	struct drm_crtc *crtc;
> > > >  	struct drm_crtc_state *crtc_state;
> > > > +	struct drm_connector *conn;
> > > > +	struct drm_connector_state *conn_state;
> > > >  	int i, ret = 0;
> > > >  
> > > >  	DRM_DEBUG_ATOMIC("checking %p\n", state);
> > > > @@ -1658,6 +1779,15 @@ int drm_atomic_check_only(struct drm_atomic_state *state)
> > > >  		}
> > > >  	}
> > > >  
> > > > +	for_each_new_connector_in_state(state, conn, conn_state, i) {
> > > > +		ret = drm_atomic_connector_check(conn, conn_state);
> > > > +		if (ret) {
> > > > +			DRM_DEBUG_ATOMIC("[CONNECTOR:%d:%s] atomic core check failed\n",
> > > > +					 conn->base.id, conn->name);
> > > > +			return ret;
> > > > +		}
> > > > +	}
> > > > +
> > > >  	if (config->funcs->atomic_check)
> > > >  		ret = config->funcs->atomic_check(state->dev, state);
> > > >  
> > > > diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c
> > > > index ae3cbfe9e01c..12b910755d84 100644
> > > > --- a/drivers/gpu/drm/drm_atomic_helper.c
> > > > +++ b/drivers/gpu/drm/drm_atomic_helper.c
> > > > @@ -30,6 +30,7 @@
> > > >  #include <drm/drm_plane_helper.h>
> > > >  #include <drm/drm_crtc_helper.h>
> > > >  #include <drm/drm_atomic_helper.h>
> > > > +#include <drm/drm_writeback.h>
> > > >  #include <linux/dma-fence.h>
> > > >  
> > > >  #include "drm_crtc_helper_internal.h"
> > > > @@ -1159,6 +1160,27 @@ void drm_atomic_helper_commit_modeset_disables(struct drm_device *dev,
> > > >  }
> > > >  EXPORT_SYMBOL(drm_atomic_helper_commit_modeset_disables);
> > > >  
> > > > +static void commit_writebacks(struct drm_device *dev, struct drm_atomic_state *old_state)
> > > > +{
> > > > +	struct drm_connector *connector;
> > > > +	struct drm_connector_state *new_conn_state;
> > > > +	int i;
> > > > +
> > > > +	for_each_new_connector_in_state(old_state, connector, new_conn_state, i) {
> > > > +		const struct drm_connector_helper_funcs *funcs;
> > > > +
> > > > +		funcs = connector->helper_private;
> > > > +
> > > > +		if (new_conn_state->writeback_job &&
> > > > +		    new_conn_state->writeback_job->fb) {
> > > > +			WARN_ON(connector->connector_type !=
> > > > +				DRM_MODE_CONNECTOR_WRITEBACK);
> > > > +			funcs->atomic_commit(connector,
> > > > +					     new_conn_state->writeback_job);
> > > > +		}
> > > > +	}
> > > > +}
> > > > +
> > > >  /**
> > > >   * drm_atomic_helper_commit_modeset_enables - modeset commit to enable outputs
> > > >   * @dev: DRM device
> > > > @@ -1238,6 +1260,8 @@ void drm_atomic_helper_commit_modeset_enables(struct drm_device *dev,
> > > >  
> > > >  		drm_bridge_enable(encoder->bridge);
> > > >  	}
> > > > +
> > > > +	commit_writebacks(dev, old_state);
> > > >  }
> > > >  EXPORT_SYMBOL(drm_atomic_helper_commit_modeset_enables);
> > > >  
> > > > @@ -3627,6 +3651,9 @@ __drm_atomic_helper_connector_duplicate_state(struct drm_connector *connector,
> > > >  	if (state->crtc)
> > > >  		drm_connector_get(connector);
> > > >  	state->commit = NULL;
> > > > +
> > > > +	/* Don't copy over a writeback job, they are used only once */
> > > > +	state->writeback_job = NULL;
> > > >  }
> > > >  EXPORT_SYMBOL(__drm_atomic_helper_connector_duplicate_state);
> > > >  
> > > > @@ -3756,6 +3783,9 @@ __drm_atomic_helper_connector_destroy_state(struct drm_connector_state *state)
> > > >  
> > > >  	if (state->commit)
> > > >  		drm_crtc_commit_put(state->commit);
> > > > +
> > > > +	if (state->writeback_job)
> > > > +		drm_writeback_cleanup_job(state->writeback_job);
> > > >  }
> > > >  EXPORT_SYMBOL(__drm_atomic_helper_connector_destroy_state);
> > > >  
> > > > diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c
> > > > index 16b9c3810af2..add47f06ae70 100644
> > > > --- a/drivers/gpu/drm/drm_connector.c
> > > > +++ b/drivers/gpu/drm/drm_connector.c
> > > > @@ -87,6 +87,7 @@ static struct drm_conn_prop_enum_list drm_connector_enum_list[] = {
> > > >  	{ DRM_MODE_CONNECTOR_VIRTUAL, "Virtual" },
> > > >  	{ DRM_MODE_CONNECTOR_DSI, "DSI" },
> > > >  	{ DRM_MODE_CONNECTOR_DPI, "DPI" },
> > > > +	{ DRM_MODE_CONNECTOR_WRITEBACK, "Writeback" },
> > > >  };
> > > >  
> > > >  void drm_connector_ida_init(void)
> > > > @@ -249,7 +250,8 @@ int drm_connector_init(struct drm_device *dev,
> > > >  	config->num_connector++;
> > > >  	spin_unlock_irq(&config->connector_list_lock);
> > > >  
> > > > -	if (connector_type != DRM_MODE_CONNECTOR_VIRTUAL)
> > > > +	if (connector_type != DRM_MODE_CONNECTOR_VIRTUAL &&
> > > > +	    connector_type != DRM_MODE_CONNECTOR_WRITEBACK)
> > > >  		drm_object_attach_property(&connector->base,
> > > >  					      config->edid_property,
> > > >  					      0);
> > > > diff --git a/drivers/gpu/drm/drm_writeback.c b/drivers/gpu/drm/drm_writeback.c
> > > > new file mode 100644
> > > > index 000000000000..da61f929cbc3
> > > > --- /dev/null
> > > > +++ b/drivers/gpu/drm/drm_writeback.c
> > > > @@ -0,0 +1,257 @@
> > > > +/*
> > > > + * (C) COPYRIGHT 2016 ARM Limited. All rights reserved.
> > > > + * Author: Brian Starkey <brian.starkey@arm.com>
> > > > + *
> > > > + * This program is free software and is provided to you under the terms of the
> > > > + * GNU General Public License version 2 as published by the Free Software
> > > > + * Foundation, and any use by you of this program is subject to the terms
> > > > + * of such GNU licence.
> > > > + */
> > > > +
> > > > +#include <drm/drm_crtc.h>
> > > > +#include <drm/drm_modeset_helper_vtables.h>
> > > > +#include <drm/drm_property.h>
> > > > +#include <drm/drm_writeback.h>
> > > > +#include <drm/drmP.h>
> > > > +
> > > > +/**
> > > > + * DOC: overview
> > > > + *
> > > > + * Writeback connectors are used to expose hardware which can write the output
> > > > + * from a CRTC to a memory buffer. They are used and act similarly to other
> > > > + * types of connectors, with some important differences:
> > > > + *  - Writeback connectors don't provide a way to output visually to the user.
> > > > + *  - Writeback connectors should always report as "disconnected" (so that
> > > > + *    clients which don't understand them will ignore them).
> > > 
> > > Have we considered hiding writeback behind a client cap instead?
> > 
> > Yes, changelog says that the client cap was dropped in v2 based on
> > feedback (as far as I remember). 
> > 
> > > 
> > > > + *  - Writeback connectors don't have EDID.
> > > > + *
> > > > + * A framebuffer may only be attached to a writeback connector when the
> > > > + * connector is attached to a CRTC. The WRITEBACK_FB_ID property which sets the
> > > > + * framebuffer applies only to a single commit (see below). A framebuffer may
> > > > + * not be attached while the CRTC is off.
> > > > + *
> > > > + * Writeback connectors have some additional properties, which userspace
> > > > + * can use to query and control them:
> > > > + *
> > > > + *  "WRITEBACK_FB_ID":
> > > > + *	Write-only object property storing a DRM_MODE_OBJECT_FB: it stores the
> > > > + *	framebuffer to be written by the writeback connector. This property is
> > > > + *	similar to the FB_ID property on planes, but will always read as zero
> > > > + *	and is not preserved across commits.
> > > > + *	Userspace must set this property to an output buffer every time it
> > > > + *	wishes the buffer to get filled.
> > > > + *
> > > > + *  "WRITEBACK_PIXEL_FORMATS":
> > > > + *	Immutable blob property to store the supported pixel formats table. The
> > > > + *	data is an array of u32 DRM_FORMAT_* fourcc values.
> > > > + *	Userspace can use this blob to find out what pixel formats are supported
> > > > + *	by the connector's writeback engine.
> > > > + */
> > > > +
> > > > +static bool create_writeback_properties(struct drm_device *dev)
> > > > +{
> > > > +	struct drm_property *prop;
> > > > +
> > > > +	if (!dev->mode_config.writeback_fb_id_property) {
> > > 
> > > Somewhat an aside, but we should probably come up with more formal rules on
> > > where to stick properties. We have some in drm_connector, which I interpret as
> > > "connector-only properties", but we also have a bunch in mode_config.
> > > 
> > > For optional properties which will generally only apply to one connector on a
> > > system, I'd be inclined to put it in connector. 
> > 
> > I can move the writeback-related properties into the connector in the
> > next series.
> > 
> 
> Either way works for me, no need to do this if you're happy with it, I was just
> idly musing.
> 
> > 
> > > 
> > > > +		prop = drm_property_create_object(dev, DRM_MODE_PROP_ATOMIC,
> > > > +						  "WRITEBACK_FB_ID",
> > > > +						  DRM_MODE_OBJECT_FB);
> > > > +		if (!prop)
> > > > +			return false;
> > > > +		dev->mode_config.writeback_fb_id_property = prop;
> > > > +	}
> > > > +
> > > > +	if (!dev->mode_config.writeback_pixel_formats_property) {
> > > > +		prop = drm_property_create(dev, DRM_MODE_PROP_BLOB | DRM_MODE_PROP_IMMUTABLE,
> > > > +					   "WRITEBACK_PIXEL_FORMATS", 0);
> > > 
> > > Does it make sense to expose the formats if u/s doesn't support atomic and can't
> > > use writeback? Perhaps this should also be DRM_MODE_PROP_ATOMIC.
> > 
> > Agree, probably all the writeback properties should only be available if
> > the client selects DRM_MODE_PROP_ATOMIC.
> > 
> > > 
> > > 
> > > > +		if (!prop)
> > > > +			return false;
> > > > +		dev->mode_config.writeback_pixel_formats_property = prop;
> > > > +	}
> > > > +
> > > > +	return true;
> > > > +}
> > > > +
> > > > +static const struct drm_encoder_funcs drm_writeback_encoder_funcs = {
> > > > +	.destroy = drm_encoder_cleanup,
> > > > +};
> > > > +
> > > > +/**
> > > > + * drm_writeback_connector_init - Initialize a writeback connector and its properties
> > > > + * @dev: DRM device
> > > > + * @wb_connector: Writeback connector to initialize
> > > > + * @con_funcs: Connector funcs vtable
> > > > + * @enc_helper_funcs: Encoder helper funcs vtable to be used by the internal encoder
> > > > + * @formats: Array of supported pixel formats for the writeback engine
> > > > + * @n_formats: Length of the formats array
> > > > + *
> > > > + * This function creates the writeback-connector-specific properties if they
> > > > + * have not been already created, initializes the connector as
> > > > + * type DRM_MODE_CONNECTOR_WRITEBACK, and correctly initializes the property
> > > > + * values. It will also create an internal encoder associated with the
> > > > + * drm_writeback_connector and set it to use the @enc_helper_funcs vtable for
> > > > + * the encoder helper.
> > > > + *
> > > > + * Drivers should always use this function instead of drm_connector_init() to
> > > > + * set up writeback connectors.
> > > > + *
> > > > + * Returns: 0 on success, or a negative error code
> > > > + */
> > > > +int drm_writeback_connector_init(struct drm_device *dev,
> > > > +				 struct drm_writeback_connector *wb_connector,
> > > > +				 const struct drm_connector_funcs *con_funcs,
> > > > +				 const struct drm_encoder_helper_funcs *enc_helper_funcs,
> > > > +				 const u32 *formats, int n_formats)
> > > > +{
> > > > +	int ret;
> > > > +	struct drm_property_blob *blob;
> > > > +	struct drm_connector *connector = &wb_connector->base;
> > > > +	struct drm_mode_config *config = &dev->mode_config;
> > > > +
> > > > +	if (!create_writeback_properties(dev))
> > > > +		return -EINVAL;
> > > > +
> > > > +	blob = drm_property_create_blob(dev, n_formats * sizeof(*formats),
> > > > +					formats);
> > > > +	if (IS_ERR(blob))
> > > > +		return PTR_ERR(blob);
> > > > +
> > > > +	drm_encoder_helper_add(&wb_connector->encoder, enc_helper_funcs);
> > > > +	ret = drm_encoder_init(dev, &wb_connector->encoder,
> > > > +			       &drm_writeback_encoder_funcs,
> > > > +			       DRM_MODE_ENCODER_VIRTUAL, NULL);
> > > > +	if (ret)
> > > > +		goto fail;
> > > > +
> > > > +	connector->interlace_allowed = 0;
> > > > +
> > > > +	ret = drm_connector_init(dev, connector, con_funcs,
> > > > +				 DRM_MODE_CONNECTOR_WRITEBACK);
> > > > +	if (ret)
> > > > +		goto connector_fail;
> > > > +
> > > > +	ret = drm_mode_connector_attach_encoder(connector,
> > > > +						&wb_connector->encoder);
> > > > +	if (ret)
> > > > +		goto attach_fail;
> > > > +
> > > > +	INIT_LIST_HEAD(&wb_connector->job_queue);
> > > > +	spin_lock_init(&wb_connector->job_lock);
> > > > +
> > > > +	drm_object_attach_property(&connector->base,
> > > > +				   config->writeback_fb_id_property, 0);
> > > > +
> > > > +	drm_object_attach_property(&connector->base,
> > > > +				   config->writeback_pixel_formats_property,
> > > > +				   blob->base.id);
> > > > +	wb_connector->pixel_formats_blob_ptr = blob;
> > > > +
> > > > +	return 0;
> > > > +
> > > > +attach_fail:
> > > > +	drm_connector_cleanup(connector);
> > > > +connector_fail:
> > > > +	drm_encoder_cleanup(&wb_connector->encoder);
> > > > +fail:
> > > > +	drm_property_unreference_blob(blob);
> > > > +	return ret;
> > > > +}
> > > > +EXPORT_SYMBOL(drm_writeback_connector_init);
> > > > +
> > > > +/**
> > > > + * drm_writeback_queue_job - Queue a writeback job for later signalling
> > > > + * @wb_connector: The writeback connector to queue a job on
> > > > + * @job: The job to queue
> > > > + *
> > > > + * This function adds a job to the job_queue for a writeback connector. It
> > > > + * should be considered to take ownership of the writeback job, and so any other
> > > > + * references to the job must be cleared after calling this function.
> > > > + *
> > > > + * Drivers must ensure that for a given writeback connector, jobs are queued in
> > > > + * exactly the same order as they will be completed by the hardware (and
> > > > + * signaled via drm_writeback_signal_completion).
> > > > + *
> > > > + * For every call to drm_writeback_queue_job() there must be exactly one call to
> > > > + * drm_writeback_signal_completion()
> > > > + *
> > > > + * See also: drm_writeback_signal_completion()
> > > > + */
> > > > +void drm_writeback_queue_job(struct drm_writeback_connector *wb_connector,
> > > > +			     struct drm_writeback_job *job)
> > > > +{
> > > > +	unsigned long flags;
> > > > +
> > > > +	spin_lock_irqsave(&wb_connector->job_lock, flags);
> > > > +	list_add_tail(&job->list_entry, &wb_connector->job_queue);
> > > > +	spin_unlock_irqrestore(&wb_connector->job_lock, flags);
> > > > +}
> > > > +EXPORT_SYMBOL(drm_writeback_queue_job);
> > > > +
> > > > +/**
> > > > + * drm_writeback_cleanup_job - Cleanup and free a writeback job
> > > > + * @job: The writeback job to free
> > > > + *
> > > > + * Drops any references held by the writeback job, and frees the structure.
> > > > + */
> > > > +void drm_writeback_cleanup_job(struct drm_writeback_job *job)
> > > > +{
> > > > +	if (!job)
> > > > +		return;
> > > > +
> > > > +	if (job->fb)
> > > > +		drm_framebuffer_unreference(job->fb);
> > > > +	kfree(job);
> > > > +}
> > > > +EXPORT_SYMBOL(drm_writeback_cleanup_job);
> > > > +
> > > > +/*
> > > > + * @cleanup_work: deferred cleanup of a writeback job
> > > > + *
> > > > + * The job cannot be cleaned up directly in drm_writeback_signal_completion,
> > > > + * because it may be called in interrupt context. Dropping the framebuffer
> > > > + * reference can sleep, and so the cleanup is deferred to a workqueue.
> > > > + */
> > > > +static void cleanup_work(struct work_struct *work)
> > > > +{
> > > > +	struct drm_writeback_job *job = container_of(work,
> > > > +						     struct drm_writeback_job,
> > > > +						     cleanup_work);
> > > > +	drm_writeback_cleanup_job(job);
> > > 
> > > TIL that you can free work while using it.
> > 
> > Was not aware that it is possible. I can update the code to do it.
> 
> That's what's happening now, afaict. drm_writeback_cleanup_job() frees job, which
> contains work in this function.

Sorry for the confusion, I was not familiar with TIL and assumed to mean
something actionable/positive. Now I've learned that it stands for
"today I('ve) learned".

Yeah, surprised that we can get away with it :)


Best regards,
Liviu

> 
> Sean
> 
> > 
> > Thanks for the feedback and review!
> > 
> > Best regards,
> > Liviu
> > 
> > > 
> > > > +}
> > > > +
> > > > +/**
> > > > + * drm_writeback_signal_completion - Signal the completion of a writeback job
> > > > + * @wb_connector: The writeback connector whose job is complete
> > > > + *
> > > > + * Drivers should call this to signal the completion of a previously queued
> > > > + * writeback job. It should be called as soon as possible after the hardware
> > > > + * has finished writing, and may be called from interrupt context.
> > > > + * It is the driver's responsibility to ensure that for a given connector, the
> > > > + * hardware completes writeback jobs in the same order as they are queued.
> > > > + *
> > > > + * Unless the driver is holding its own reference to the framebuffer, it must
> > > > + * not be accessed after calling this function.
> > > > + *
> > > > + * See also: drm_writeback_queue_job()
> > > > + */
> > > > +void
> > > > +drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector)
> > > > +{
> > > > +	unsigned long flags;
> > > > +	struct drm_writeback_job *job;
> > > > +
> > > > +	spin_lock_irqsave(&wb_connector->job_lock, flags);
> > > > +	job = list_first_entry_or_null(&wb_connector->job_queue,
> > > > +				       struct drm_writeback_job,
> > > > +				       list_entry);
> > > > +	if (job)
> > > > +		list_del(&job->list_entry);
> > > > +	spin_unlock_irqrestore(&wb_connector->job_lock, flags);
> > > > +
> > > > +	if (WARN_ON(!job))
> > > > +		return;
> > > > +
> > > > +	INIT_WORK(&job->cleanup_work, cleanup_work);
> > > > +	queue_work(system_long_wq, &job->cleanup_work);
> > > > +}
> > > > +EXPORT_SYMBOL(drm_writeback_signal_completion);
> > > > diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
> > > > index cf13842a6dbd..d7b0263cc5cf 100644
> > > > --- a/include/drm/drm_atomic.h
> > > > +++ b/include/drm/drm_atomic.h
> > > > @@ -594,6 +594,9 @@ void drm_atomic_set_fence_for_plane(struct drm_plane_state *plane_state,
> > > >  int __must_check
> > > >  drm_atomic_set_crtc_for_connector(struct drm_connector_state *conn_state,
> > > >  				  struct drm_crtc *crtc);
> > > > +int drm_atomic_set_writeback_fb_for_connector(
> > > > +		struct drm_connector_state *conn_state,
> > > > +		struct drm_framebuffer *fb);
> > > >  int __must_check
> > > >  drm_atomic_add_affected_connectors(struct drm_atomic_state *state,
> > > >  				   struct drm_crtc *crtc);
> > > > diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
> > > > index 758a176e7b57..8701ebcc68b3 100644
> > > > --- a/include/drm/drm_connector.h
> > > > +++ b/include/drm/drm_connector.h
> > > > @@ -425,6 +425,19 @@ struct drm_connector_state {
> > > >  	 * protection. This is most commonly used for HDCP.
> > > >  	 */
> > > >  	unsigned int content_protection;
> > > > +
> > > > +	/**
> > > > +	 * @writeback_job: Writeback job for writeback connectors
> > > > +	 *
> > > > +	 * Holds the framebuffer for a writeback connector. As the writeback
> > > > +	 * completion may be asynchronous to the normal commit cycle, the
> > > > +	 * writeback job lifetime is managed separately from the normal atomic
> > > > +	 * state by this object.
> > > > +	 *
> > > > +	 * See also: drm_writeback_queue_job() and
> > > > +	 * drm_writeback_signal_completion()
> > > > +	 */
> > > > +	struct drm_writeback_job *writeback_job;
> > > >  };
> > > >  
> > > >  /**
> > > > diff --git a/include/drm/drm_mode_config.h b/include/drm/drm_mode_config.h
> > > > index 7569f22ffef6..c012e1148ec0 100644
> > > > --- a/include/drm/drm_mode_config.h
> > > > +++ b/include/drm/drm_mode_config.h
> > > > @@ -779,6 +779,20 @@ struct drm_mode_config {
> > > >  	 */
> > > >  	struct drm_property *panel_orientation_property;
> > > >  
> > > > +	/**
> > > > +	 * @writeback_fb_id_property: Property for writeback connectors, storing
> > > > +	 * the ID of the output framebuffer.
> > > > +	 * See also: drm_writeback_connector_init()
> > > > +	 */
> > > > +	struct drm_property *writeback_fb_id_property;
> > > > +	/**
> > > > +	 * @writeback_pixel_formats_property: Property for writeback connectors,
> > > > +	 * storing an array of the supported pixel formats for the writeback
> > > > +	 * engine (read-only).
> > > > +	 * See also: drm_writeback_connector_init()
> > > > +	 */
> > > > +	struct drm_property *writeback_pixel_formats_property;
> > > > +
> > > >  	/* dumb ioctl parameters */
> > > >  	uint32_t preferred_depth, prefer_shadow;
> > > >  
> > > > diff --git a/include/drm/drm_modeset_helper_vtables.h b/include/drm/drm_modeset_helper_vtables.h
> > > > index 3e76ca805b0f..97d3a810bc85 100644
> > > > --- a/include/drm/drm_modeset_helper_vtables.h
> > > > +++ b/include/drm/drm_modeset_helper_vtables.h
> > > > @@ -974,6 +974,17 @@ struct drm_connector_helper_funcs {
> > > >  	 */
> > > >  	int (*atomic_check)(struct drm_connector *connector,
> > > >  			    struct drm_connector_state *state);
> > > > +
> > > > +	/**
> > > > +	 * @atomic_commit:
> > > > +	 *
> > > > +	 * For write-back connectors, this is the point to commit the write-
> > > > +	 * back job to hw.
> > > > +	 *
> > > > +	 * This callback is used by the atomic modeset helpers.
> > > > +	 */
> > > > +	void (*atomic_commit)(struct drm_connector *connector,
> > > > +			      struct drm_writeback_job *writeback_job);
> > > >  };
> > > >  
> > > >  /**
> > > > diff --git a/include/drm/drm_writeback.h b/include/drm/drm_writeback.h
> > > > new file mode 100644
> > > > index 000000000000..0bb95fd4907d
> > > > --- /dev/null
> > > > +++ b/include/drm/drm_writeback.h
> > > > @@ -0,0 +1,89 @@
> > > > +/*
> > > > + * (C) COPYRIGHT 2016 ARM Limited. All rights reserved.
> > > > + * Author: Brian Starkey <brian.starkey@arm.com>
> > > > + *
> > > > + * This program is free software and is provided to you under the terms of the
> > > > + * GNU General Public License version 2 as published by the Free Software
> > > > + * Foundation, and any use by you of this program is subject to the terms
> > > > + * of such GNU licence.
> > > > + */
> > > > +
> > > > +#ifndef __DRM_WRITEBACK_H__
> > > > +#define __DRM_WRITEBACK_H__
> > > > +#include <drm/drm_connector.h>
> > > > +#include <drm/drm_encoder.h>
> > > > +#include <linux/workqueue.h>
> > > > +
> > > > +struct drm_writeback_connector {
> > > > +	struct drm_connector base;
> > > > +
> > > > +	/**
> > > > +	 * @encoder: Internal encoder used by the connector to fulfill
> > > > +	 * the DRM framework requirements. The users of the
> > > > +	 * @drm_writeback_connector control the behaviour of the @encoder
> > > > +	 * by passing the @enc_funcs parameter to drm_writeback_connector_init()
> > > > +	 * function.
> > > > +	 */
> > > > +	struct drm_encoder encoder;
> > > > +
> > > > +	/**
> > > > +	 * @pixel_formats_blob_ptr:
> > > > +	 *
> > > > +	 * DRM blob property data for the pixel formats list on writeback
> > > > +	 * connectors
> > > > +	 * See also drm_writeback_connector_init()
> > > > +	 */
> > > > +	struct drm_property_blob *pixel_formats_blob_ptr;
> > > > +
> > > > +	/** @job_lock: Protects job_queue */
> > > > +	spinlock_t job_lock;
> > > > +
> > > > +	/**
> > > > +	 * @job_queue:
> > > > +	 *
> > > > +	 * Holds a list of a connector's writeback jobs; the last item is the
> > > > +	 * most recent. The first item may be either waiting for the hardware
> > > > +	 * to begin writing, or currently being written.
> > > > +	 *
> > > > +	 * See also: drm_writeback_queue_job() and
> > > > +	 * drm_writeback_signal_completion()
> > > > +	 */
> > > > +	struct list_head job_queue;
> > > > +};
> > > > +#define to_wb_connector(x) container_of(x, struct drm_writeback_connector, base)
> > > > +
> > > > +struct drm_writeback_job {
> > > > +	/**
> > > > +	 * @cleanup_work:
> > > > +	 *
> > > > +	 * Used to allow drm_writeback_signal_completion to defer dropping the
> > > > +	 * framebuffer reference to a workqueue.
> > > > +	 */
> > > > +	struct work_struct cleanup_work;
> > > > +	/**
> > > > +	 * @list_entry:
> > > > +	 *
> > > > +	 * List item for the connector's @job_queue
> > > > +	 */
> > > > +	struct list_head list_entry;
> > > > +	/**
> > > > +	 * @fb:
> > > > +	 *
> > > > +	 * Framebuffer to be written to by the writeback connector. Do not set
> > > > +	 * directly, use drm_atomic_set_writeback_fb_for_connector()
> > > > +	 */
> > > > +	struct drm_framebuffer *fb;
> > > > +};
> > > > +
> > > > +int drm_writeback_connector_init(struct drm_device *dev,
> > > > +				 struct drm_writeback_connector *wb_connector,
> > > > +				 const struct drm_connector_funcs *con_funcs,
> > > > +				 const struct drm_encoder_helper_funcs *enc_helper_funcs,
> > > > +				 const u32 *formats, int n_formats);
> > > > +
> > > > +void drm_writeback_queue_job(struct drm_writeback_connector *wb_connector,
> > > > +			     struct drm_writeback_job *job);
> > > > +
> > > > +void drm_writeback_cleanup_job(struct drm_writeback_job *job);
> > > > +void drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector);
> > > > +#endif
> > > > diff --git a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h
> > > > index 2c575794fb52..7b47e184e95e 100644
> > > > --- a/include/uapi/drm/drm_mode.h
> > > > +++ b/include/uapi/drm/drm_mode.h
> > > > @@ -338,6 +338,7 @@ enum drm_mode_subconnector {
> > > >  #define DRM_MODE_CONNECTOR_VIRTUAL      15
> > > >  #define DRM_MODE_CONNECTOR_DSI		16
> > > >  #define DRM_MODE_CONNECTOR_DPI		17
> > > > +#define DRM_MODE_CONNECTOR_WRITEBACK	18
> > > >  
> > > >  struct drm_mode_get_connector {
> > > >  
> > > > -- 
> > > > 2.14.3
> > > > 
> > > 
> > > -- 
> > > Sean Paul, Software Engineer, Google / Chromium OS
> > 
> > -- 
> > ====================
> > | I would like to |
> > | fix the world,  |
> > | but they're not |
> > | giving me the   |
> >  \ source code!  /
> >   ---------------
> >     ¯\_(ツ)_/¯
> 
> -- 
> Sean Paul, Software Engineer, Google / Chromium OS

-- 
====================
| I would like to |
| fix the world,  |
| but they're not |
| giving me the   |
 \ source code!  /
  ---------------
    ¯\_(ツ)_/¯
_______________________________________________
Freedreno mailing list
Freedreno@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/freedreno

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [RFC 1/4] drm: Add writeback connector type
@ 2018-02-23 16:52                 ` Liviu Dudau
  0 siblings, 0 replies; 41+ messages in thread
From: Liviu Dudau @ 2018-02-23 16:52 UTC (permalink / raw)
  To: Sean Paul
  Cc: Rob Clark, dri-devel, freedreno, linux-arm-msm, Brian Starkey,
	Mihail Atanassov, Gustavo Padovan, Maarten Lankhorst,
	David Airlie, Jonathan Corbet, linux-doc, linux-kernel

On Fri, Feb 23, 2018 at 11:39:06AM -0500, Sean Paul wrote:
> On Fri, Feb 23, 2018 at 04:21:05PM +0000, Liviu Dudau wrote:
> > On Fri, Feb 23, 2018 at 10:59:35AM -0500, Sean Paul wrote:
> > > On Fri, Feb 23, 2018 at 08:17:51AM -0500, Rob Clark wrote:
> > > > From: Brian Starkey <brian.starkey@arm.com>
> > > > 
> > > > Writeback connectors represent writeback engines which can write the
> > > > CRTC output to a memory framebuffer. Add a writeback connector type and
> > > > related support functions.
> > > > 
> > > > Drivers should initialize a writeback connector with
> > > > drm_writeback_connector_init() which takes care of setting up all the
> > > > writeback-specific details on top of the normal functionality of
> > > > drm_connector_init().
> > > > 
> > > > Writeback connectors have a WRITEBACK_FB_ID property, used to set the
> > > > output framebuffer, and a WRITEBACK_PIXEL_FORMATS blob used to expose the
> > > > supported writeback formats to userspace.
> > > > 
> > > > When a framebuffer is attached to a writeback connector with the
> > > > WRITEBACK_FB_ID property, it is used only once (for the commit in which
> > > > it was included), and userspace can never read back the value of
> > > > WRITEBACK_FB_ID. WRITEBACK_FB_ID can only be set if the connector is
> > > > attached to a CRTC.
> > > > 
> > > > Changes since v1:
> > > >  - Added drm_writeback.c + documentation
> > > >  - Added helper to initialize writeback connector in one go
> > > >  - Added core checks
> > > >  - Squashed into a single commit
> > > >  - Dropped the client cap
> > > >  - Writeback framebuffers are no longer persistent
> > > > 
> > > > Changes since v2:
> > > >  Daniel Vetter:
> > > >  - Subclass drm_connector to drm_writeback_connector
> > > >  - Relax check to allow CRTC to be set without an FB
> > > >  - Add some writeback_ prefixes
> > > >  - Drop PIXEL_FORMATS_SIZE property, as it was unnecessary
> > > >  Gustavo Padovan:
> > > >  - Add drm_writeback_job to handle writeback signalling centrally
> > > > 
> > > > Changes since v3:
> > > >  - Rebased
> > > >  - Rename PIXEL_FORMATS -> WRITEBACK_PIXEL_FORMATS
> > > > 
> > > > Changes since v4:
> > > >  - Added atomic_commit() vfunc to connector helper funcs, so that
> > > >    writeback jobs are committed from atomic helpers
> > > > 
> > > > Signed-off-by: Brian Starkey <brian.starkey@arm.com>
> > > > [rebased and fixed conflicts]
> > > > Signed-off-by: Mihail Atanassov <mihail.atanassov@arm.com>
> > > > Signed-off-by: Liviu Dudau <liviu.dudau@arm.com>
> > > > [rebased and added atomic_commit() vfunc for writeback jobs]
> > > > Signed-off-by: Rob Clark <robdclark@gmail.com>
> > > > ---
> > > >  Documentation/gpu/drm-kms.rst            |   9 ++
> > > >  drivers/gpu/drm/Makefile                 |   2 +-
> > > >  drivers/gpu/drm/drm_atomic.c             | 130 ++++++++++++++++
> > > >  drivers/gpu/drm/drm_atomic_helper.c      |  30 ++++
> > > >  drivers/gpu/drm/drm_connector.c          |   4 +-
> > > >  drivers/gpu/drm/drm_writeback.c          | 257 +++++++++++++++++++++++++++++++
> > > >  include/drm/drm_atomic.h                 |   3 +
> > > >  include/drm/drm_connector.h              |  13 ++
> > > >  include/drm/drm_mode_config.h            |  14 ++
> > > >  include/drm/drm_modeset_helper_vtables.h |  11 ++
> > > >  include/drm/drm_writeback.h              |  89 +++++++++++
> > > >  include/uapi/drm/drm_mode.h              |   1 +
> > > >  12 files changed, 561 insertions(+), 2 deletions(-)
> > > >  create mode 100644 drivers/gpu/drm/drm_writeback.c
> > > >  create mode 100644 include/drm/drm_writeback.h
> > > > 
> > > > diff --git a/Documentation/gpu/drm-kms.rst b/Documentation/gpu/drm-kms.rst
> > > > index 2dcf5b42015d..e7590dd2f71e 100644
> > > > --- a/Documentation/gpu/drm-kms.rst
> > > > +++ b/Documentation/gpu/drm-kms.rst
> > > > @@ -370,6 +370,15 @@ Connector Functions Reference
> > > >  .. kernel-doc:: drivers/gpu/drm/drm_connector.c
> > > >     :export:
> > > >  
> > > > +Writeback Connectors
> > > > +--------------------
> > > > +
> > > > +.. kernel-doc:: drivers/gpu/drm/drm_writeback.c
> > > > +  :doc: overview
> > > > +
> > > > +.. kernel-doc:: drivers/gpu/drm/drm_writeback.c
> > > > +  :export:
> > > > +
> > > >  Encoder Abstraction
> > > >  ===================
> > > >  
> > > > diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> > > > index 50093ff4479b..3d708959b224 100644
> > > > --- a/drivers/gpu/drm/Makefile
> > > > +++ b/drivers/gpu/drm/Makefile
> > > > @@ -18,7 +18,7 @@ drm-y       :=	drm_auth.o drm_bufs.o drm_cache.o \
> > > >  		drm_encoder.o drm_mode_object.o drm_property.o \
> > > >  		drm_plane.o drm_color_mgmt.o drm_print.o \
> > > >  		drm_dumb_buffers.o drm_mode_config.o drm_vblank.o \
> > > > -		drm_syncobj.o drm_lease.o
> > > > +		drm_syncobj.o drm_lease.o drm_writeback.o
> > > >  
> > > >  drm-$(CONFIG_DRM_LIB_RANDOM) += lib/drm_random.o
> > > >  drm-$(CONFIG_DRM_VM) += drm_vm.o
> > > > diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
> > > > index 46733d534587..019f131fe8be 100644
> > > > --- a/drivers/gpu/drm/drm_atomic.c
> > > > +++ b/drivers/gpu/drm/drm_atomic.c
> > > > @@ -30,6 +30,7 @@
> > > >  #include <drm/drm_atomic.h>
> > > >  #include <drm/drm_mode.h>
> > > >  #include <drm/drm_print.h>
> > > > +#include <drm/drm_writeback.h>
> > > >  #include <linux/sync_file.h>
> > > >  
> > > >  #include "drm_crtc_internal.h"
> > > > @@ -638,6 +639,46 @@ static void drm_atomic_crtc_print_state(struct drm_printer *p,
> > > >  		crtc->funcs->atomic_print_state(p, state);
> > > >  }
> > > >  
> > > > +/**
> > > > + * drm_atomic_connector_check - check connector state
> > > > + * @connector: connector to check
> > > > + * @state: connector state to check
> > > > + *
> > > > + * Provides core sanity checks for connector state.
> > > > + *
> > > > + * RETURNS:
> > > > + * Zero on success, error code on failure
> > > > + */
> > > > +static int drm_atomic_connector_check(struct drm_connector *connector,
> > > > +		struct drm_connector_state *state)
> > > > +{
> > > > +	struct drm_crtc_state *crtc_state;
> > > > +	struct drm_writeback_job *writeback_job = state->writeback_job;
> > > > +
> > > > +	if ((connector->connector_type != DRM_MODE_CONNECTOR_WRITEBACK) ||
> > > > +	    !writeback_job)
> > > > +		return 0;
> > > > +
> > > > +	if (writeback_job->fb && !state->crtc) {
> > > > +		DRM_DEBUG_ATOMIC("[CONNECTOR:%d:%s] framebuffer without CRTC\n",
> > > > +				 connector->base.id, connector->name);
> > > > +		return -EINVAL;
> > > > +	}
> > > > +
> > > > +	if (state->crtc)
> > > > +		crtc_state = drm_atomic_get_existing_crtc_state(state->state,
> > > > +								state->crtc);
> > > > +
> > > > +	if (writeback_job->fb && !crtc_state->active) {
> > > 
> > > Checking for writeback_job->fb is redundant here and above since it's checked
> > > first thing as early exit criteria.
> > 
> > I somehow don't see that check. We check if (!writebac_job) and then all
> > other checks are logic ands with other conditions, so I think the check
> > here is still relevant.
> 
> Ah, yeah, the first check doesn't check for ->fb (I imagined it on the end of
> writeback_job. At any rate, if you add a check for !->fb in the first conditional
> you don't need it here or in the second check.
> 
> > 
> > > 
> > > > +		DRM_DEBUG_ATOMIC("[CONNECTOR:%d:%s] has framebuffer, but [CRTC:%d] is off\n",
> > > > +				 connector->base.id, connector->name,
> > > > +				 state->crtc->base.id);
> > > > +		return -EINVAL;
> > > > +	}
> > > > +
> > > > +	return 0;
> > > > +}
> > > > +
> > > >  /**
> > > >   * drm_atomic_get_plane_state - get plane state
> > > >   * @state: global atomic state object
> > > > @@ -1230,6 +1271,12 @@ static int drm_atomic_connector_set_property(struct drm_connector *connector,
> > > >  			return -EINVAL;
> > > >  		}
> > > >  		state->content_protection = val;
> > > > +	} else if (property == config->writeback_fb_id_property) {
> > > > +		struct drm_framebuffer *fb = drm_framebuffer_lookup(dev, NULL, val);
> > > > +		int ret = drm_atomic_set_writeback_fb_for_connector(state, fb);
> > > > +		if (fb)
> > > > +			drm_framebuffer_unreference(fb);
> > > > +		return ret;
> > > >  	} else if (connector->funcs->atomic_set_property) {
> > > >  		return connector->funcs->atomic_set_property(connector,
> > > >  				state, property, val);
> > > > @@ -1311,6 +1358,9 @@ drm_atomic_connector_get_property(struct drm_connector *connector,
> > > >  		*val = state->scaling_mode;
> > > >  	} else if (property == connector->content_protection_property) {
> > > >  		*val = state->content_protection;
> > > > +	} else if (property == config->writeback_fb_id_property) {
> > > > +		/* Writeback framebuffer is one-shot, write and forget */
> > > > +		*val = 0;
> > > >  	} else if (connector->funcs->atomic_get_property) {
> > > >  		return connector->funcs->atomic_get_property(connector,
> > > >  				state, property, val);
> > > > @@ -1518,6 +1568,75 @@ drm_atomic_set_crtc_for_connector(struct drm_connector_state *conn_state,
> > > >  }
> > > >  EXPORT_SYMBOL(drm_atomic_set_crtc_for_connector);
> > > >  
> > > > +/*
> > > > + * drm_atomic_get_writeback_job - return or allocate a writeback job
> > > > + * @conn_state: Connector state to get the job for
> > > > + *
> > > > + * Writeback jobs have a different lifetime to the atomic state they are
> > > > + * associated with. This convenience function takes care of allocating a job
> > > > + * if there isn't yet one associated with the connector state, otherwise
> > > > + * it just returns the existing job.
> > > > + *
> > > > + * Returns: The writeback job for the given connector state
> > > > + */
> > > > +static struct drm_writeback_job *
> > > > +drm_atomic_get_writeback_job(struct drm_connector_state *conn_state)
> > > > +{
> > > > +	WARN_ON(conn_state->connector->connector_type !=
> > > > +		DRM_MODE_CONNECTOR_WRITEBACK);
> > > > +
> > > > +	if (!conn_state->writeback_job)
> > > > +		conn_state->writeback_job =
> > > > +			kzalloc(sizeof(*conn_state->writeback_job), GFP_KERNEL);
> > > > +
> > > > +	return conn_state->writeback_job;
> > > > +}
> > > > +
> > > > +/**
> > > > + * drm_atomic_set_writeback_fb_for_connector - set writeback framebuffer
> > > > + * @conn_state: atomic state object for the connector
> > > > + * @fb: fb to use for the connector
> > > > + *
> > > > + * This is used to set the framebuffer for a writeback connector, which outputs
> > > > + * to a buffer instead of an actual physical connector.
> > > > + * Changing the assigned framebuffer requires us to grab a reference to the new
> > > > + * fb and drop the reference to the old fb, if there is one. This function
> > > > + * takes care of all these details besides updating the pointer in the
> > > > + * state object itself.
> > > > + *
> > > > + * Note: The only way conn_state can already have an fb set is if the commit
> > > > + * sets the property more than once.
> > > > + *
> > > > + * See also: drm_writeback_connector_init()
> > > > + *
> > > > + * Returns: 0 on success
> > > > + */
> > > > +int drm_atomic_set_writeback_fb_for_connector(
> > > > +		struct drm_connector_state *conn_state,
> > > > +		struct drm_framebuffer *fb)
> > > > +{
> > > > +	struct drm_writeback_job *job =
> > > > +		drm_atomic_get_writeback_job(conn_state);
> > > > +	if (!job)
> > > > +		return -ENOMEM;
> > > > +
> > > > +	if (job->fb)
> > > > +		drm_framebuffer_unreference(job->fb);
> > > > +	if (fb)
> > > > +		drm_framebuffer_reference(fb);
> > > > +	job->fb = fb;
> > > > +
> > > > +	if (fb)
> > > > +		DRM_DEBUG_ATOMIC("Set [FB:%d] for connector state %p\n",
> > > > +				 fb->base.id, conn_state);
> > > > +	else
> > > > +		DRM_DEBUG_ATOMIC("Set [NOFB] for connector state %p\n",
> > > > +				 conn_state);
> > > > +
> > > > +	return 0;
> > > > +}
> > > > +EXPORT_SYMBOL(drm_atomic_set_writeback_fb_for_connector);
> > > > +
> > > >  /**
> > > >   * drm_atomic_add_affected_connectors - add connectors for crtc
> > > >   * @state: atomic state
> > > > @@ -1636,6 +1755,8 @@ int drm_atomic_check_only(struct drm_atomic_state *state)
> > > >  	struct drm_plane_state *plane_state;
> > > >  	struct drm_crtc *crtc;
> > > >  	struct drm_crtc_state *crtc_state;
> > > > +	struct drm_connector *conn;
> > > > +	struct drm_connector_state *conn_state;
> > > >  	int i, ret = 0;
> > > >  
> > > >  	DRM_DEBUG_ATOMIC("checking %p\n", state);
> > > > @@ -1658,6 +1779,15 @@ int drm_atomic_check_only(struct drm_atomic_state *state)
> > > >  		}
> > > >  	}
> > > >  
> > > > +	for_each_new_connector_in_state(state, conn, conn_state, i) {
> > > > +		ret = drm_atomic_connector_check(conn, conn_state);
> > > > +		if (ret) {
> > > > +			DRM_DEBUG_ATOMIC("[CONNECTOR:%d:%s] atomic core check failed\n",
> > > > +					 conn->base.id, conn->name);
> > > > +			return ret;
> > > > +		}
> > > > +	}
> > > > +
> > > >  	if (config->funcs->atomic_check)
> > > >  		ret = config->funcs->atomic_check(state->dev, state);
> > > >  
> > > > diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c
> > > > index ae3cbfe9e01c..12b910755d84 100644
> > > > --- a/drivers/gpu/drm/drm_atomic_helper.c
> > > > +++ b/drivers/gpu/drm/drm_atomic_helper.c
> > > > @@ -30,6 +30,7 @@
> > > >  #include <drm/drm_plane_helper.h>
> > > >  #include <drm/drm_crtc_helper.h>
> > > >  #include <drm/drm_atomic_helper.h>
> > > > +#include <drm/drm_writeback.h>
> > > >  #include <linux/dma-fence.h>
> > > >  
> > > >  #include "drm_crtc_helper_internal.h"
> > > > @@ -1159,6 +1160,27 @@ void drm_atomic_helper_commit_modeset_disables(struct drm_device *dev,
> > > >  }
> > > >  EXPORT_SYMBOL(drm_atomic_helper_commit_modeset_disables);
> > > >  
> > > > +static void commit_writebacks(struct drm_device *dev, struct drm_atomic_state *old_state)
> > > > +{
> > > > +	struct drm_connector *connector;
> > > > +	struct drm_connector_state *new_conn_state;
> > > > +	int i;
> > > > +
> > > > +	for_each_new_connector_in_state(old_state, connector, new_conn_state, i) {
> > > > +		const struct drm_connector_helper_funcs *funcs;
> > > > +
> > > > +		funcs = connector->helper_private;
> > > > +
> > > > +		if (new_conn_state->writeback_job &&
> > > > +		    new_conn_state->writeback_job->fb) {
> > > > +			WARN_ON(connector->connector_type !=
> > > > +				DRM_MODE_CONNECTOR_WRITEBACK);
> > > > +			funcs->atomic_commit(connector,
> > > > +					     new_conn_state->writeback_job);
> > > > +		}
> > > > +	}
> > > > +}
> > > > +
> > > >  /**
> > > >   * drm_atomic_helper_commit_modeset_enables - modeset commit to enable outputs
> > > >   * @dev: DRM device
> > > > @@ -1238,6 +1260,8 @@ void drm_atomic_helper_commit_modeset_enables(struct drm_device *dev,
> > > >  
> > > >  		drm_bridge_enable(encoder->bridge);
> > > >  	}
> > > > +
> > > > +	commit_writebacks(dev, old_state);
> > > >  }
> > > >  EXPORT_SYMBOL(drm_atomic_helper_commit_modeset_enables);
> > > >  
> > > > @@ -3627,6 +3651,9 @@ __drm_atomic_helper_connector_duplicate_state(struct drm_connector *connector,
> > > >  	if (state->crtc)
> > > >  		drm_connector_get(connector);
> > > >  	state->commit = NULL;
> > > > +
> > > > +	/* Don't copy over a writeback job, they are used only once */
> > > > +	state->writeback_job = NULL;
> > > >  }
> > > >  EXPORT_SYMBOL(__drm_atomic_helper_connector_duplicate_state);
> > > >  
> > > > @@ -3756,6 +3783,9 @@ __drm_atomic_helper_connector_destroy_state(struct drm_connector_state *state)
> > > >  
> > > >  	if (state->commit)
> > > >  		drm_crtc_commit_put(state->commit);
> > > > +
> > > > +	if (state->writeback_job)
> > > > +		drm_writeback_cleanup_job(state->writeback_job);
> > > >  }
> > > >  EXPORT_SYMBOL(__drm_atomic_helper_connector_destroy_state);
> > > >  
> > > > diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c
> > > > index 16b9c3810af2..add47f06ae70 100644
> > > > --- a/drivers/gpu/drm/drm_connector.c
> > > > +++ b/drivers/gpu/drm/drm_connector.c
> > > > @@ -87,6 +87,7 @@ static struct drm_conn_prop_enum_list drm_connector_enum_list[] = {
> > > >  	{ DRM_MODE_CONNECTOR_VIRTUAL, "Virtual" },
> > > >  	{ DRM_MODE_CONNECTOR_DSI, "DSI" },
> > > >  	{ DRM_MODE_CONNECTOR_DPI, "DPI" },
> > > > +	{ DRM_MODE_CONNECTOR_WRITEBACK, "Writeback" },
> > > >  };
> > > >  
> > > >  void drm_connector_ida_init(void)
> > > > @@ -249,7 +250,8 @@ int drm_connector_init(struct drm_device *dev,
> > > >  	config->num_connector++;
> > > >  	spin_unlock_irq(&config->connector_list_lock);
> > > >  
> > > > -	if (connector_type != DRM_MODE_CONNECTOR_VIRTUAL)
> > > > +	if (connector_type != DRM_MODE_CONNECTOR_VIRTUAL &&
> > > > +	    connector_type != DRM_MODE_CONNECTOR_WRITEBACK)
> > > >  		drm_object_attach_property(&connector->base,
> > > >  					      config->edid_property,
> > > >  					      0);
> > > > diff --git a/drivers/gpu/drm/drm_writeback.c b/drivers/gpu/drm/drm_writeback.c
> > > > new file mode 100644
> > > > index 000000000000..da61f929cbc3
> > > > --- /dev/null
> > > > +++ b/drivers/gpu/drm/drm_writeback.c
> > > > @@ -0,0 +1,257 @@
> > > > +/*
> > > > + * (C) COPYRIGHT 2016 ARM Limited. All rights reserved.
> > > > + * Author: Brian Starkey <brian.starkey@arm.com>
> > > > + *
> > > > + * This program is free software and is provided to you under the terms of the
> > > > + * GNU General Public License version 2 as published by the Free Software
> > > > + * Foundation, and any use by you of this program is subject to the terms
> > > > + * of such GNU licence.
> > > > + */
> > > > +
> > > > +#include <drm/drm_crtc.h>
> > > > +#include <drm/drm_modeset_helper_vtables.h>
> > > > +#include <drm/drm_property.h>
> > > > +#include <drm/drm_writeback.h>
> > > > +#include <drm/drmP.h>
> > > > +
> > > > +/**
> > > > + * DOC: overview
> > > > + *
> > > > + * Writeback connectors are used to expose hardware which can write the output
> > > > + * from a CRTC to a memory buffer. They are used and act similarly to other
> > > > + * types of connectors, with some important differences:
> > > > + *  - Writeback connectors don't provide a way to output visually to the user.
> > > > + *  - Writeback connectors should always report as "disconnected" (so that
> > > > + *    clients which don't understand them will ignore them).
> > > 
> > > Have we considered hiding writeback behind a client cap instead?
> > 
> > Yes, changelog says that the client cap was dropped in v2 based on
> > feedback (as far as I remember). 
> > 
> > > 
> > > > + *  - Writeback connectors don't have EDID.
> > > > + *
> > > > + * A framebuffer may only be attached to a writeback connector when the
> > > > + * connector is attached to a CRTC. The WRITEBACK_FB_ID property which sets the
> > > > + * framebuffer applies only to a single commit (see below). A framebuffer may
> > > > + * not be attached while the CRTC is off.
> > > > + *
> > > > + * Writeback connectors have some additional properties, which userspace
> > > > + * can use to query and control them:
> > > > + *
> > > > + *  "WRITEBACK_FB_ID":
> > > > + *	Write-only object property storing a DRM_MODE_OBJECT_FB: it stores the
> > > > + *	framebuffer to be written by the writeback connector. This property is
> > > > + *	similar to the FB_ID property on planes, but will always read as zero
> > > > + *	and is not preserved across commits.
> > > > + *	Userspace must set this property to an output buffer every time it
> > > > + *	wishes the buffer to get filled.
> > > > + *
> > > > + *  "WRITEBACK_PIXEL_FORMATS":
> > > > + *	Immutable blob property to store the supported pixel formats table. The
> > > > + *	data is an array of u32 DRM_FORMAT_* fourcc values.
> > > > + *	Userspace can use this blob to find out what pixel formats are supported
> > > > + *	by the connector's writeback engine.
> > > > + */
> > > > +
> > > > +static bool create_writeback_properties(struct drm_device *dev)
> > > > +{
> > > > +	struct drm_property *prop;
> > > > +
> > > > +	if (!dev->mode_config.writeback_fb_id_property) {
> > > 
> > > Somewhat an aside, but we should probably come up with more formal rules on
> > > where to stick properties. We have some in drm_connector, which I interpret as
> > > "connector-only properties", but we also have a bunch in mode_config.
> > > 
> > > For optional properties which will generally only apply to one connector on a
> > > system, I'd be inclined to put it in connector. 
> > 
> > I can move the writeback-related properties into the connector in the
> > next series.
> > 
> 
> Either way works for me, no need to do this if you're happy with it, I was just
> idly musing.
> 
> > 
> > > 
> > > > +		prop = drm_property_create_object(dev, DRM_MODE_PROP_ATOMIC,
> > > > +						  "WRITEBACK_FB_ID",
> > > > +						  DRM_MODE_OBJECT_FB);
> > > > +		if (!prop)
> > > > +			return false;
> > > > +		dev->mode_config.writeback_fb_id_property = prop;
> > > > +	}
> > > > +
> > > > +	if (!dev->mode_config.writeback_pixel_formats_property) {
> > > > +		prop = drm_property_create(dev, DRM_MODE_PROP_BLOB | DRM_MODE_PROP_IMMUTABLE,
> > > > +					   "WRITEBACK_PIXEL_FORMATS", 0);
> > > 
> > > Does it make sense to expose the formats if u/s doesn't support atomic and can't
> > > use writeback? Perhaps this should also be DRM_MODE_PROP_ATOMIC.
> > 
> > Agree, probably all the writeback properties should only be available if
> > the client selects DRM_MODE_PROP_ATOMIC.
> > 
> > > 
> > > 
> > > > +		if (!prop)
> > > > +			return false;
> > > > +		dev->mode_config.writeback_pixel_formats_property = prop;
> > > > +	}
> > > > +
> > > > +	return true;
> > > > +}
> > > > +
> > > > +static const struct drm_encoder_funcs drm_writeback_encoder_funcs = {
> > > > +	.destroy = drm_encoder_cleanup,
> > > > +};
> > > > +
> > > > +/**
> > > > + * drm_writeback_connector_init - Initialize a writeback connector and its properties
> > > > + * @dev: DRM device
> > > > + * @wb_connector: Writeback connector to initialize
> > > > + * @con_funcs: Connector funcs vtable
> > > > + * @enc_helper_funcs: Encoder helper funcs vtable to be used by the internal encoder
> > > > + * @formats: Array of supported pixel formats for the writeback engine
> > > > + * @n_formats: Length of the formats array
> > > > + *
> > > > + * This function creates the writeback-connector-specific properties if they
> > > > + * have not been already created, initializes the connector as
> > > > + * type DRM_MODE_CONNECTOR_WRITEBACK, and correctly initializes the property
> > > > + * values. It will also create an internal encoder associated with the
> > > > + * drm_writeback_connector and set it to use the @enc_helper_funcs vtable for
> > > > + * the encoder helper.
> > > > + *
> > > > + * Drivers should always use this function instead of drm_connector_init() to
> > > > + * set up writeback connectors.
> > > > + *
> > > > + * Returns: 0 on success, or a negative error code
> > > > + */
> > > > +int drm_writeback_connector_init(struct drm_device *dev,
> > > > +				 struct drm_writeback_connector *wb_connector,
> > > > +				 const struct drm_connector_funcs *con_funcs,
> > > > +				 const struct drm_encoder_helper_funcs *enc_helper_funcs,
> > > > +				 const u32 *formats, int n_formats)
> > > > +{
> > > > +	int ret;
> > > > +	struct drm_property_blob *blob;
> > > > +	struct drm_connector *connector = &wb_connector->base;
> > > > +	struct drm_mode_config *config = &dev->mode_config;
> > > > +
> > > > +	if (!create_writeback_properties(dev))
> > > > +		return -EINVAL;
> > > > +
> > > > +	blob = drm_property_create_blob(dev, n_formats * sizeof(*formats),
> > > > +					formats);
> > > > +	if (IS_ERR(blob))
> > > > +		return PTR_ERR(blob);
> > > > +
> > > > +	drm_encoder_helper_add(&wb_connector->encoder, enc_helper_funcs);
> > > > +	ret = drm_encoder_init(dev, &wb_connector->encoder,
> > > > +			       &drm_writeback_encoder_funcs,
> > > > +			       DRM_MODE_ENCODER_VIRTUAL, NULL);
> > > > +	if (ret)
> > > > +		goto fail;
> > > > +
> > > > +	connector->interlace_allowed = 0;
> > > > +
> > > > +	ret = drm_connector_init(dev, connector, con_funcs,
> > > > +				 DRM_MODE_CONNECTOR_WRITEBACK);
> > > > +	if (ret)
> > > > +		goto connector_fail;
> > > > +
> > > > +	ret = drm_mode_connector_attach_encoder(connector,
> > > > +						&wb_connector->encoder);
> > > > +	if (ret)
> > > > +		goto attach_fail;
> > > > +
> > > > +	INIT_LIST_HEAD(&wb_connector->job_queue);
> > > > +	spin_lock_init(&wb_connector->job_lock);
> > > > +
> > > > +	drm_object_attach_property(&connector->base,
> > > > +				   config->writeback_fb_id_property, 0);
> > > > +
> > > > +	drm_object_attach_property(&connector->base,
> > > > +				   config->writeback_pixel_formats_property,
> > > > +				   blob->base.id);
> > > > +	wb_connector->pixel_formats_blob_ptr = blob;
> > > > +
> > > > +	return 0;
> > > > +
> > > > +attach_fail:
> > > > +	drm_connector_cleanup(connector);
> > > > +connector_fail:
> > > > +	drm_encoder_cleanup(&wb_connector->encoder);
> > > > +fail:
> > > > +	drm_property_unreference_blob(blob);
> > > > +	return ret;
> > > > +}
> > > > +EXPORT_SYMBOL(drm_writeback_connector_init);
> > > > +
> > > > +/**
> > > > + * drm_writeback_queue_job - Queue a writeback job for later signalling
> > > > + * @wb_connector: The writeback connector to queue a job on
> > > > + * @job: The job to queue
> > > > + *
> > > > + * This function adds a job to the job_queue for a writeback connector. It
> > > > + * should be considered to take ownership of the writeback job, and so any other
> > > > + * references to the job must be cleared after calling this function.
> > > > + *
> > > > + * Drivers must ensure that for a given writeback connector, jobs are queued in
> > > > + * exactly the same order as they will be completed by the hardware (and
> > > > + * signaled via drm_writeback_signal_completion).
> > > > + *
> > > > + * For every call to drm_writeback_queue_job() there must be exactly one call to
> > > > + * drm_writeback_signal_completion()
> > > > + *
> > > > + * See also: drm_writeback_signal_completion()
> > > > + */
> > > > +void drm_writeback_queue_job(struct drm_writeback_connector *wb_connector,
> > > > +			     struct drm_writeback_job *job)
> > > > +{
> > > > +	unsigned long flags;
> > > > +
> > > > +	spin_lock_irqsave(&wb_connector->job_lock, flags);
> > > > +	list_add_tail(&job->list_entry, &wb_connector->job_queue);
> > > > +	spin_unlock_irqrestore(&wb_connector->job_lock, flags);
> > > > +}
> > > > +EXPORT_SYMBOL(drm_writeback_queue_job);
> > > > +
> > > > +/**
> > > > + * drm_writeback_cleanup_job - Cleanup and free a writeback job
> > > > + * @job: The writeback job to free
> > > > + *
> > > > + * Drops any references held by the writeback job, and frees the structure.
> > > > + */
> > > > +void drm_writeback_cleanup_job(struct drm_writeback_job *job)
> > > > +{
> > > > +	if (!job)
> > > > +		return;
> > > > +
> > > > +	if (job->fb)
> > > > +		drm_framebuffer_unreference(job->fb);
> > > > +	kfree(job);
> > > > +}
> > > > +EXPORT_SYMBOL(drm_writeback_cleanup_job);
> > > > +
> > > > +/*
> > > > + * @cleanup_work: deferred cleanup of a writeback job
> > > > + *
> > > > + * The job cannot be cleaned up directly in drm_writeback_signal_completion,
> > > > + * because it may be called in interrupt context. Dropping the framebuffer
> > > > + * reference can sleep, and so the cleanup is deferred to a workqueue.
> > > > + */
> > > > +static void cleanup_work(struct work_struct *work)
> > > > +{
> > > > +	struct drm_writeback_job *job = container_of(work,
> > > > +						     struct drm_writeback_job,
> > > > +						     cleanup_work);
> > > > +	drm_writeback_cleanup_job(job);
> > > 
> > > TIL that you can free work while using it.
> > 
> > Was not aware that it is possible. I can update the code to do it.
> 
> That's what's happening now, afaict. drm_writeback_cleanup_job() frees job, which
> contains work in this function.

Sorry for the confusion, I was not familiar with TIL and assumed to mean
something actionable/positive. Now I've learned that it stands for
"today I('ve) learned".

Yeah, surprised that we can get away with it :)


Best regards,
Liviu

> 
> Sean
> 
> > 
> > Thanks for the feedback and review!
> > 
> > Best regards,
> > Liviu
> > 
> > > 
> > > > +}
> > > > +
> > > > +/**
> > > > + * drm_writeback_signal_completion - Signal the completion of a writeback job
> > > > + * @wb_connector: The writeback connector whose job is complete
> > > > + *
> > > > + * Drivers should call this to signal the completion of a previously queued
> > > > + * writeback job. It should be called as soon as possible after the hardware
> > > > + * has finished writing, and may be called from interrupt context.
> > > > + * It is the driver's responsibility to ensure that for a given connector, the
> > > > + * hardware completes writeback jobs in the same order as they are queued.
> > > > + *
> > > > + * Unless the driver is holding its own reference to the framebuffer, it must
> > > > + * not be accessed after calling this function.
> > > > + *
> > > > + * See also: drm_writeback_queue_job()
> > > > + */
> > > > +void
> > > > +drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector)
> > > > +{
> > > > +	unsigned long flags;
> > > > +	struct drm_writeback_job *job;
> > > > +
> > > > +	spin_lock_irqsave(&wb_connector->job_lock, flags);
> > > > +	job = list_first_entry_or_null(&wb_connector->job_queue,
> > > > +				       struct drm_writeback_job,
> > > > +				       list_entry);
> > > > +	if (job)
> > > > +		list_del(&job->list_entry);
> > > > +	spin_unlock_irqrestore(&wb_connector->job_lock, flags);
> > > > +
> > > > +	if (WARN_ON(!job))
> > > > +		return;
> > > > +
> > > > +	INIT_WORK(&job->cleanup_work, cleanup_work);
> > > > +	queue_work(system_long_wq, &job->cleanup_work);
> > > > +}
> > > > +EXPORT_SYMBOL(drm_writeback_signal_completion);
> > > > diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
> > > > index cf13842a6dbd..d7b0263cc5cf 100644
> > > > --- a/include/drm/drm_atomic.h
> > > > +++ b/include/drm/drm_atomic.h
> > > > @@ -594,6 +594,9 @@ void drm_atomic_set_fence_for_plane(struct drm_plane_state *plane_state,
> > > >  int __must_check
> > > >  drm_atomic_set_crtc_for_connector(struct drm_connector_state *conn_state,
> > > >  				  struct drm_crtc *crtc);
> > > > +int drm_atomic_set_writeback_fb_for_connector(
> > > > +		struct drm_connector_state *conn_state,
> > > > +		struct drm_framebuffer *fb);
> > > >  int __must_check
> > > >  drm_atomic_add_affected_connectors(struct drm_atomic_state *state,
> > > >  				   struct drm_crtc *crtc);
> > > > diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
> > > > index 758a176e7b57..8701ebcc68b3 100644
> > > > --- a/include/drm/drm_connector.h
> > > > +++ b/include/drm/drm_connector.h
> > > > @@ -425,6 +425,19 @@ struct drm_connector_state {
> > > >  	 * protection. This is most commonly used for HDCP.
> > > >  	 */
> > > >  	unsigned int content_protection;
> > > > +
> > > > +	/**
> > > > +	 * @writeback_job: Writeback job for writeback connectors
> > > > +	 *
> > > > +	 * Holds the framebuffer for a writeback connector. As the writeback
> > > > +	 * completion may be asynchronous to the normal commit cycle, the
> > > > +	 * writeback job lifetime is managed separately from the normal atomic
> > > > +	 * state by this object.
> > > > +	 *
> > > > +	 * See also: drm_writeback_queue_job() and
> > > > +	 * drm_writeback_signal_completion()
> > > > +	 */
> > > > +	struct drm_writeback_job *writeback_job;
> > > >  };
> > > >  
> > > >  /**
> > > > diff --git a/include/drm/drm_mode_config.h b/include/drm/drm_mode_config.h
> > > > index 7569f22ffef6..c012e1148ec0 100644
> > > > --- a/include/drm/drm_mode_config.h
> > > > +++ b/include/drm/drm_mode_config.h
> > > > @@ -779,6 +779,20 @@ struct drm_mode_config {
> > > >  	 */
> > > >  	struct drm_property *panel_orientation_property;
> > > >  
> > > > +	/**
> > > > +	 * @writeback_fb_id_property: Property for writeback connectors, storing
> > > > +	 * the ID of the output framebuffer.
> > > > +	 * See also: drm_writeback_connector_init()
> > > > +	 */
> > > > +	struct drm_property *writeback_fb_id_property;
> > > > +	/**
> > > > +	 * @writeback_pixel_formats_property: Property for writeback connectors,
> > > > +	 * storing an array of the supported pixel formats for the writeback
> > > > +	 * engine (read-only).
> > > > +	 * See also: drm_writeback_connector_init()
> > > > +	 */
> > > > +	struct drm_property *writeback_pixel_formats_property;
> > > > +
> > > >  	/* dumb ioctl parameters */
> > > >  	uint32_t preferred_depth, prefer_shadow;
> > > >  
> > > > diff --git a/include/drm/drm_modeset_helper_vtables.h b/include/drm/drm_modeset_helper_vtables.h
> > > > index 3e76ca805b0f..97d3a810bc85 100644
> > > > --- a/include/drm/drm_modeset_helper_vtables.h
> > > > +++ b/include/drm/drm_modeset_helper_vtables.h
> > > > @@ -974,6 +974,17 @@ struct drm_connector_helper_funcs {
> > > >  	 */
> > > >  	int (*atomic_check)(struct drm_connector *connector,
> > > >  			    struct drm_connector_state *state);
> > > > +
> > > > +	/**
> > > > +	 * @atomic_commit:
> > > > +	 *
> > > > +	 * For write-back connectors, this is the point to commit the write-
> > > > +	 * back job to hw.
> > > > +	 *
> > > > +	 * This callback is used by the atomic modeset helpers.
> > > > +	 */
> > > > +	void (*atomic_commit)(struct drm_connector *connector,
> > > > +			      struct drm_writeback_job *writeback_job);
> > > >  };
> > > >  
> > > >  /**
> > > > diff --git a/include/drm/drm_writeback.h b/include/drm/drm_writeback.h
> > > > new file mode 100644
> > > > index 000000000000..0bb95fd4907d
> > > > --- /dev/null
> > > > +++ b/include/drm/drm_writeback.h
> > > > @@ -0,0 +1,89 @@
> > > > +/*
> > > > + * (C) COPYRIGHT 2016 ARM Limited. All rights reserved.
> > > > + * Author: Brian Starkey <brian.starkey@arm.com>
> > > > + *
> > > > + * This program is free software and is provided to you under the terms of the
> > > > + * GNU General Public License version 2 as published by the Free Software
> > > > + * Foundation, and any use by you of this program is subject to the terms
> > > > + * of such GNU licence.
> > > > + */
> > > > +
> > > > +#ifndef __DRM_WRITEBACK_H__
> > > > +#define __DRM_WRITEBACK_H__
> > > > +#include <drm/drm_connector.h>
> > > > +#include <drm/drm_encoder.h>
> > > > +#include <linux/workqueue.h>
> > > > +
> > > > +struct drm_writeback_connector {
> > > > +	struct drm_connector base;
> > > > +
> > > > +	/**
> > > > +	 * @encoder: Internal encoder used by the connector to fulfill
> > > > +	 * the DRM framework requirements. The users of the
> > > > +	 * @drm_writeback_connector control the behaviour of the @encoder
> > > > +	 * by passing the @enc_funcs parameter to drm_writeback_connector_init()
> > > > +	 * function.
> > > > +	 */
> > > > +	struct drm_encoder encoder;
> > > > +
> > > > +	/**
> > > > +	 * @pixel_formats_blob_ptr:
> > > > +	 *
> > > > +	 * DRM blob property data for the pixel formats list on writeback
> > > > +	 * connectors
> > > > +	 * See also drm_writeback_connector_init()
> > > > +	 */
> > > > +	struct drm_property_blob *pixel_formats_blob_ptr;
> > > > +
> > > > +	/** @job_lock: Protects job_queue */
> > > > +	spinlock_t job_lock;
> > > > +
> > > > +	/**
> > > > +	 * @job_queue:
> > > > +	 *
> > > > +	 * Holds a list of a connector's writeback jobs; the last item is the
> > > > +	 * most recent. The first item may be either waiting for the hardware
> > > > +	 * to begin writing, or currently being written.
> > > > +	 *
> > > > +	 * See also: drm_writeback_queue_job() and
> > > > +	 * drm_writeback_signal_completion()
> > > > +	 */
> > > > +	struct list_head job_queue;
> > > > +};
> > > > +#define to_wb_connector(x) container_of(x, struct drm_writeback_connector, base)
> > > > +
> > > > +struct drm_writeback_job {
> > > > +	/**
> > > > +	 * @cleanup_work:
> > > > +	 *
> > > > +	 * Used to allow drm_writeback_signal_completion to defer dropping the
> > > > +	 * framebuffer reference to a workqueue.
> > > > +	 */
> > > > +	struct work_struct cleanup_work;
> > > > +	/**
> > > > +	 * @list_entry:
> > > > +	 *
> > > > +	 * List item for the connector's @job_queue
> > > > +	 */
> > > > +	struct list_head list_entry;
> > > > +	/**
> > > > +	 * @fb:
> > > > +	 *
> > > > +	 * Framebuffer to be written to by the writeback connector. Do not set
> > > > +	 * directly, use drm_atomic_set_writeback_fb_for_connector()
> > > > +	 */
> > > > +	struct drm_framebuffer *fb;
> > > > +};
> > > > +
> > > > +int drm_writeback_connector_init(struct drm_device *dev,
> > > > +				 struct drm_writeback_connector *wb_connector,
> > > > +				 const struct drm_connector_funcs *con_funcs,
> > > > +				 const struct drm_encoder_helper_funcs *enc_helper_funcs,
> > > > +				 const u32 *formats, int n_formats);
> > > > +
> > > > +void drm_writeback_queue_job(struct drm_writeback_connector *wb_connector,
> > > > +			     struct drm_writeback_job *job);
> > > > +
> > > > +void drm_writeback_cleanup_job(struct drm_writeback_job *job);
> > > > +void drm_writeback_signal_completion(struct drm_writeback_connector *wb_connector);
> > > > +#endif
> > > > diff --git a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h
> > > > index 2c575794fb52..7b47e184e95e 100644
> > > > --- a/include/uapi/drm/drm_mode.h
> > > > +++ b/include/uapi/drm/drm_mode.h
> > > > @@ -338,6 +338,7 @@ enum drm_mode_subconnector {
> > > >  #define DRM_MODE_CONNECTOR_VIRTUAL      15
> > > >  #define DRM_MODE_CONNECTOR_DSI		16
> > > >  #define DRM_MODE_CONNECTOR_DPI		17
> > > > +#define DRM_MODE_CONNECTOR_WRITEBACK	18
> > > >  
> > > >  struct drm_mode_get_connector {
> > > >  
> > > > -- 
> > > > 2.14.3
> > > > 
> > > 
> > > -- 
> > > Sean Paul, Software Engineer, Google / Chromium OS
> > 
> > -- 
> > ====================
> > | I would like to |
> > | fix the world,  |
> > | but they're not |
> > | giving me the   |
> >  \ source code!  /
> >   ---------------
> >     ¯\_(ツ)_/¯
> 
> -- 
> Sean Paul, Software Engineer, Google / Chromium OS

-- 
====================
| I would like to |
| fix the world,  |
| but they're not |
| giving me the   |
 \ source code!  /
  ---------------
    ¯\_(ツ)_/¯

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [RFC 1/4] drm: Add writeback connector type
  2018-02-23 16:48               ` Liviu Dudau
@ 2018-02-23 17:04                   ` Sean Paul
  -1 siblings, 0 replies; 41+ messages in thread
From: Sean Paul @ 2018-02-23 17:04 UTC (permalink / raw)
  To: Liviu Dudau
  Cc: Jonathan Corbet, David Airlie, linux-arm-msm,
	linux-doc-u79uwXL29TY76Z2rM5mHXA, Maarten Lankhorst,
	Linux Kernel Mailing List, dri-devel, daniel.vetter-/w4YWyX8dFk,
	Rob Clark, Sean Paul, Gustavo Padovan, Mihail Atanassov,
	freedreno, Brian Starkey

On Fri, Feb 23, 2018 at 04:48:58PM +0000, Liviu Dudau wrote:
> On Fri, Feb 23, 2018 at 11:43:29AM -0500, Sean Paul wrote:
> > On Fri, Feb 23, 2018 at 11:25:11AM -0500, Rob Clark wrote:
> > > On Fri, Feb 23, 2018 at 10:59 AM, Sean Paul <seanpaul@chromium.org> wrote:
> > > >
> > > > Have we considered hiding writeback behind a client cap instead?
> > > 
> > > It is kinda *almost* unneeded, since the connector reports itself as
> > > disconnected.
> > > 
> > > I'm not sure what the reason was to drop the cap, but I think it would
> > > be better to have a cap so WB connectors don't show up in, for ex,
> > > xrandr
> > 
> > Yeah, the disconnected hack is kind of gross, IMO. I hate to introduce churn in
> > the patch series given that it was initially introduced with the client cap.
> 
> Haha, that's the reverse of Daniel's position:
> 
> https://lists.freedesktop.org/archives/dri-devel/2016-October/120519.html

Yeah, it happens :(. I don't think it's a dealbreaker either way, it just seems
awkward to expose a connector which is "disconnected", but available for use. I
don't think we have any other connectors which are supposed to be used in this
state.

> 
> > 
> > There are also cases where we might want to make writeback unavailable, such as
> > when content protection is enabled. In those cases, it's conceivable that we
> > might want to use disconnected as a signal to u/s. I suppose we could also just
> > fail the check, so most of this is just academic.
> 
> Not sure what other hardware out there does, but on Mali DP's case you
> would be outputing the protected content by putting the display
> processor in secure mode, which automatically disables writeback for us.
> Or to put in another way, you don't need a writeback framebuffer if you
> are in non-secure mode as you can get access to the framebuffer used for
> the plane anyway.

Yeah, I was mostly thinking about the case where you might have HDCP enabled on
the HDMI connector and be able to slurp up the content via a writeback. However
if the buffer is not secure in the first place, then it's already accessible in
userspace so I don't think that writeback presents a new security hole.

/me needs to get HDCP out of his head.

Sean


> 
> Best regards,
> Liviu
> 
> > 
> > Sean
> > 
> > 
> > > 
> > > BR,
> > > -R
> > 
> > -- 
> > Sean Paul, Software Engineer, Google / Chromium OS
> 
> -- 
> ====================
> | I would like to |
> | fix the world,  |
> | but they're not |
> | giving me the   |
>  \ source code!  /
>   ---------------
>     ¯\_(ツ)_/¯

-- 
Sean Paul, Software Engineer, Google / Chromium OS
_______________________________________________
Freedreno mailing list
Freedreno@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/freedreno

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [RFC 1/4] drm: Add writeback connector type
@ 2018-02-23 17:04                   ` Sean Paul
  0 siblings, 0 replies; 41+ messages in thread
From: Sean Paul @ 2018-02-23 17:04 UTC (permalink / raw)
  To: Liviu Dudau
  Cc: Sean Paul, Rob Clark, dri-devel, freedreno, linux-arm-msm,
	Brian Starkey, Mihail Atanassov, Gustavo Padovan,
	Maarten Lankhorst, David Airlie, Jonathan Corbet, linux-doc,
	Linux Kernel Mailing List, daniel.vetter

On Fri, Feb 23, 2018 at 04:48:58PM +0000, Liviu Dudau wrote:
> On Fri, Feb 23, 2018 at 11:43:29AM -0500, Sean Paul wrote:
> > On Fri, Feb 23, 2018 at 11:25:11AM -0500, Rob Clark wrote:
> > > On Fri, Feb 23, 2018 at 10:59 AM, Sean Paul <seanpaul@chromium.org> wrote:
> > > >
> > > > Have we considered hiding writeback behind a client cap instead?
> > > 
> > > It is kinda *almost* unneeded, since the connector reports itself as
> > > disconnected.
> > > 
> > > I'm not sure what the reason was to drop the cap, but I think it would
> > > be better to have a cap so WB connectors don't show up in, for ex,
> > > xrandr
> > 
> > Yeah, the disconnected hack is kind of gross, IMO. I hate to introduce churn in
> > the patch series given that it was initially introduced with the client cap.
> 
> Haha, that's the reverse of Daniel's position:
> 
> https://lists.freedesktop.org/archives/dri-devel/2016-October/120519.html

Yeah, it happens :(. I don't think it's a dealbreaker either way, it just seems
awkward to expose a connector which is "disconnected", but available for use. I
don't think we have any other connectors which are supposed to be used in this
state.

> 
> > 
> > There are also cases where we might want to make writeback unavailable, such as
> > when content protection is enabled. In those cases, it's conceivable that we
> > might want to use disconnected as a signal to u/s. I suppose we could also just
> > fail the check, so most of this is just academic.
> 
> Not sure what other hardware out there does, but on Mali DP's case you
> would be outputing the protected content by putting the display
> processor in secure mode, which automatically disables writeback for us.
> Or to put in another way, you don't need a writeback framebuffer if you
> are in non-secure mode as you can get access to the framebuffer used for
> the plane anyway.

Yeah, I was mostly thinking about the case where you might have HDCP enabled on
the HDMI connector and be able to slurp up the content via a writeback. However
if the buffer is not secure in the first place, then it's already accessible in
userspace so I don't think that writeback presents a new security hole.

/me needs to get HDCP out of his head.

Sean


> 
> Best regards,
> Liviu
> 
> > 
> > Sean
> > 
> > 
> > > 
> > > BR,
> > > -R
> > 
> > -- 
> > Sean Paul, Software Engineer, Google / Chromium OS
> 
> -- 
> ====================
> | I would like to |
> | fix the world,  |
> | but they're not |
> | giving me the   |
>  \ source code!  /
>   ---------------
>     ¯\_(ツ)_/¯

-- 
Sean Paul, Software Engineer, Google / Chromium OS

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [RFC 4/4] drm/msm/mdp5: writeback support
  2018-02-23 16:30         ` Sean Paul
@ 2018-02-23 18:15           ` Rob Clark
  -1 siblings, 0 replies; 41+ messages in thread
From: Rob Clark @ 2018-02-23 18:15 UTC (permalink / raw)
  To: Sean Paul
  Cc: Archit Taneja, Laurent Pinchart, Neil Armstrong, David Airlie,
	linux-arm-msm, Liviu Dudau, Linux Kernel Mailing List, dri-devel,
	Sushmita Susheelendra, Daniel Vetter, freedreno, Brian Starkey,
	Ville Syrjälä

On Fri, Feb 23, 2018 at 11:30 AM, Sean Paul <seanpaul@chromium.org> wrote:
> On Fri, Feb 23, 2018 at 08:17:54AM -0500, Rob Clark wrote:
>> In a way, based on the original writeback patch from Jilai Wang, but a
>> lot has shifted around since then.
>>
>> Signed-off-by: Rob Clark <robdclark@gmail.com>
>> ---
>>  drivers/gpu/drm/msm/Makefile              |   1 +
>>  drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c |  23 +-
>>  drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c  |  38 +++-
>>  drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.h  |   7 +-
>>  drivers/gpu/drm/msm/disp/mdp5/mdp5_wb.c   | 367 ++++++++++++++++++++++++++++++
>>  drivers/gpu/drm/msm/dsi/dsi_host.c        |   4 +-
>>  6 files changed, 431 insertions(+), 9 deletions(-)
>>  create mode 100644 drivers/gpu/drm/msm/disp/mdp5/mdp5_wb.c
>>
>> diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
>> index cd40c050b2d7..c9f50adef2db 100644
>> --- a/drivers/gpu/drm/msm/Makefile
>> +++ b/drivers/gpu/drm/msm/Makefile
>> @@ -45,6 +45,7 @@ msm-y := \
>>       disp/mdp5/mdp5_mixer.o \
>>       disp/mdp5/mdp5_plane.o \
>>       disp/mdp5/mdp5_smp.o \
>> +     disp/mdp5/mdp5_wb.o \
>>       msm_atomic.o \
>>       msm_debugfs.o \
>>       msm_drv.o \
>> diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c
>> index 9893e43ba6c5..b00ca88b741d 100644
>> --- a/drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c
>> +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c
>> @@ -484,7 +484,11 @@ static void mdp5_crtc_atomic_enable(struct drm_crtc *crtc,
>>       }
>>
>>       /* Restore vblank irq handling after power is enabled */
>> -     drm_crtc_vblank_on(crtc);
>> +// TODO we can't ->get_scanout_pos() for wb (since virtual intf)..
>> +// perhaps drm core should be clever enough not to drm_reset_vblank_timestamp()
>> +// for virtual encoders / writeback?
>> +     if (mdp5_cstate->pipeline.intf->type != INTF_WB)
>> +             drm_crtc_vblank_on(crtc);
>>
>>       mdp5_crtc_mode_set_nofb(crtc);
>>
>> @@ -518,7 +522,11 @@ int mdp5_crtc_setup_pipeline(struct drm_crtc *crtc,
>>               u32 caps;
>>               int ret;
>>
>> -             caps = MDP_LM_CAP_DISPLAY;
>> +             if (pipeline->intf->type == INTF_WB)
>> +                     caps = MDP_LM_CAP_WB;
>> +             else
>> +                     caps = MDP_LM_CAP_DISPLAY;
>> +
>>               if (need_right_mixer)
>>                       caps |= MDP_LM_CAP_PAIR;
>>
>> @@ -545,6 +553,7 @@ int mdp5_crtc_setup_pipeline(struct drm_crtc *crtc,
>>       mdp5_cstate->err_irqmask = intf2err(intf->num);
>>       mdp5_cstate->vblank_irqmask = intf2vblank(pipeline->mixer, intf);
>>
>> +// XXX should we be treating WB as cmd_mode??
>>       if ((intf->type == INTF_DSI) &&
>>           (intf->mode == MDP5_INTF_DSI_MODE_COMMAND)) {
>>               mdp5_cstate->pp_done_irqmask = lm2ppdone(pipeline->mixer);
>> @@ -639,8 +648,12 @@ static int mdp5_crtc_atomic_check(struct drm_crtc *crtc,
>>       }
>>
>>       /* bail out early if there aren't any planes */
>> -     if (!cnt)
>> -             return 0;
>> +     if (!cnt) {
>> +             if (!state->active)
>> +                     return 0;
>> +             dev_err(dev->dev, "%s has no planes!\n", crtc->name);
>> +             return -EINVAL;
>> +     }
>
> This seems unrelated?
>

hmm, yeah, kinda.  It was a case that I hit before working out all the
bugs in my kmscube writeback mode, but I guess isn't directly related
to wb.

>>
>>       hw_cfg = mdp5_cfg_get_hw_config(mdp5_kms->cfg);
>>
>> @@ -1160,7 +1173,7 @@ void mdp5_crtc_wait_for_commit_done(struct drm_crtc *crtc)
>>
>>       if (mdp5_cstate->cmd_mode)
>>               mdp5_crtc_wait_for_pp_done(crtc);
>> -     else
>> +     else if (mdp5_cstate->pipeline.intf->type != INTF_WB)
>>               mdp5_crtc_wait_for_flush_done(crtc);
>>  }
>>
>> diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c
>> index 1f44d8f15ce1..239010905637 100644
>> --- a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c
>> +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c
>> @@ -427,7 +427,8 @@ static int modeset_init(struct mdp5_kms *mdp5_kms)
>>        * the MDP5 interfaces) than the number of layer mixers present in HW,
>>        * but let's be safe here anyway
>>        */
>> -     num_crtcs = min(priv->num_encoders, mdp5_kms->num_hwmixers);
>> +     num_crtcs = min(priv->num_encoders + hw_cfg->wb.count,
>> +                     mdp5_kms->num_hwmixers);
>>
>>       /*
>>        * Construct planes equaling the number of hw pipes, and CRTCs for the
>> @@ -482,6 +483,33 @@ static int modeset_init(struct mdp5_kms *mdp5_kms)
>>               encoder->possible_crtcs = (1 << priv->num_crtcs) - 1;
>>       }
>>
>> +     /*
>> +      * Lastly, construct writeback connectors.
>> +      */
>> +     for (i = 0; i < hw_cfg->wb.count; i++) {
>> +             struct drm_writeback_connector *wb_conn;
>> +             struct mdp5_ctl *ctl;
>> +
>> +             ctl = mdp5_ctlm_request(mdp5_kms->ctlm, -1);
>> +             if (!ctl) {
>> +                     dev_err(dev->dev,
>> +                             "failed to allocate ctl for writeback %d\n", i);
>> +                     continue;
>> +             }
>> +
>> +             wb_conn = mdp5_wb_connector_init(dev, ctl,
>> +                             hw_cfg->wb.instances[i].id);
>> +             if (IS_ERR(wb_conn)) {
>> +                     ret = PTR_ERR(wb_conn);
>> +                     dev_err(dev->dev,
>> +                             "failed to construct writeback connector %d (%d)\n",
>> +                             i, ret);
>> +                     goto fail;
>> +             }
>> +
>> +             wb_conn->encoder.possible_crtcs = (1 << priv->num_crtcs) - 1;
>> +     }
>> +
>>       return 0;
>>
>>  fail:
>> @@ -555,6 +583,10 @@ static bool mdp5_get_scanoutpos(struct drm_device *dev, unsigned int pipe,
>>               return false;
>>       }
>>
>> +     /* unsupported for writeback: */
>> +     if (encoder->encoder_type == DRM_MODE_ENCODER_VIRTUAL)
>> +             return false;
>> +
>>       vsw = mode->crtc_vsync_end - mode->crtc_vsync_start;
>>       vbp = mode->crtc_vtotal - mode->crtc_vsync_end;
>>
>> @@ -610,6 +642,10 @@ static u32 mdp5_get_vblank_counter(struct drm_device *dev, unsigned int pipe)
>>       if (!encoder)
>>               return 0;
>>
>> +     /* unsupported for writeback: */
>> +     if (encoder->encoder_type == DRM_MODE_ENCODER_VIRTUAL)
>> +             return 0;
>> +
>>       return mdp5_encoder_get_framecount(encoder);
>>  }
>>
>> diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.h b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.h
>> index 425a03d213e5..be0f93ef33e1 100644
>> --- a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.h
>> +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.h
>> @@ -18,6 +18,8 @@
>>  #ifndef __MDP5_KMS_H__
>>  #define __MDP5_KMS_H__
>>
>> +#include <drm/drm_writeback.h>
>> +
>>  #include "msm_drv.h"
>>  #include "msm_kms.h"
>>  #include "disp/mdp_kms.h"
>> @@ -251,7 +253,7 @@ static inline uint32_t intf2vblank(struct mdp5_hw_mixer *mixer,
>>               return MDP5_IRQ_PING_PONG_0_RD_PTR << mixer->pp;
>>
>>       if (intf->type == INTF_WB)
>> -             return MDP5_IRQ_WB_2_DONE;
>> +             return MDP5_IRQ_WB_2_DONE | MDP5_IRQ_WB_0_DONE | MDP5_IRQ_WB_1_DONE;
>>
>>       switch (intf->num) {
>>       case 0:  return MDP5_IRQ_INTF0_VSYNC;
>> @@ -330,4 +332,7 @@ static inline int mdp5_cmd_encoder_set_split_display(
>>  }
>>  #endif
>>
>> +struct drm_writeback_connector *mdp5_wb_connector_init(struct drm_device *dev,
>> +             struct mdp5_ctl *ctl, unsigned wb_id);
>> +
>>  #endif /* __MDP5_KMS_H__ */
>> diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_wb.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_wb.c
>> new file mode 100644
>> index 000000000000..3dabd0a1aa8b
>> --- /dev/null
>> +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_wb.c
>> @@ -0,0 +1,367 @@
>> +/*
>> + * Copyright (C) 2018 Red Hat
>> + * Author: Rob Clark <robdclark@gmail.com>
>> + *
>> + * This program is free software; you can redistribute it and/or modify it
>> + * under the terms of the GNU General Public License version 2 as published by
>> + * the Free Software Foundation.
>> + *
>> + * This program is distributed in the hope that it will be useful, but WITHOUT
>> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
>> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
>> + * more details.
>> + *
>> + * You should have received a copy of the GNU General Public License along with
>> + * this program.  If not, see <http://www.gnu.org/licenses/>.
>
> SPDX license
>
>> + */
>> +
>> +#include "mdp5_kms.h"
>> +
>> +/*
>> + * Writeback connector/encoder implementation:
>> + */
>> +
>> +struct mdp5_wb_connector {
>> +     struct drm_writeback_connector base;
>> +
>> +     u32 nformats;
>> +     u32 formats[32];
>> +
>> +     unsigned id;
>> +     struct mdp5_ctl *ctl;
>> +     struct mdp5_interface *intf;
>> +
>> +     struct mdp_irq wb_done;
>> +};
>> +#define to_mdp5_wb_connector(x) container_of(x, struct mdp5_wb_connector, base)
>> +
>> +
>> +static void mdp5_wb_connector_atomic_commit(struct drm_connector *connector,
>> +             struct drm_writeback_job *job);
>> +
>> +static int mdp5_wb_connector_get_modes(struct drm_connector *connector)
>> +{
>> +     struct drm_device *dev = connector->dev;
>> +
>> +     return drm_add_modes_noedid(connector, dev->mode_config.max_width,
>> +                     dev->mode_config.max_height);
>
> I guess the plan is to let userspace provide user-defined modes for wb? Is it
> ever useful to have this token mode?

/me shrugs

I just copied this from the malidp wb patch.  This actually does add
multiple modes (all the dmt modes that fit within max width/height).
Not sure if that is useful or not.

>
>> +}
>> +
>> +static enum drm_mode_status
>> +mdp5_wb_connector_mode_valid(struct drm_connector *connector,
>> +             struct drm_display_mode *mode)
>> +{
>> +     struct drm_device *dev = connector->dev;
>> +     struct drm_mode_config *mode_config = &dev->mode_config;
>> +     int w = mode->hdisplay, h = mode->vdisplay;
>> +
>> +     if ((w < mode_config->min_width) || (w > mode_config->max_width))
>> +             return MODE_BAD_HVALUE;
>> +
>> +     if ((h < mode_config->min_height) || (h > mode_config->max_height))
>> +             return MODE_BAD_VVALUE;
>> +
>> +     return MODE_OK;
>> +}
>
> Might be useful for these to migrate into drm_writeback_connector.c as helpers.

Even the _get_modes(), tbh, since I think all of these ended up the
same as malidp..

>
>> +
>> +const struct drm_connector_helper_funcs mdp5_wb_connector_helper_funcs = {
>> +     .get_modes = mdp5_wb_connector_get_modes,
>> +     .mode_valid = mdp5_wb_connector_mode_valid,
>> +     .atomic_commit = mdp5_wb_connector_atomic_commit,
>> +};
>> +
>> +static enum drm_connector_status
>> +mdp5_wb_connector_detect(struct drm_connector *connector, bool force)
>> +{
>> +     return connector_status_disconnected;
>> +}
>
> This should be a helper as well.
>
>> +
>> +static void mdp5_wb_connector_destroy(struct drm_connector *connector)
>> +{
>> +     drm_connector_cleanup(connector);
>> +}
>
> Just use drm_connector_cleanup directly below
>

actually, I seem to have lost a fixup, there is some memory to free in
_destroy()

And I suspect we need a drm_writeback_connector_cleanup()..

>> +
>> +static const struct drm_connector_funcs mdp5_wb_connector_funcs = {
>> +     .reset = drm_atomic_helper_connector_reset,
>> +     .detect = mdp5_wb_connector_detect,
>> +     .fill_modes = drm_helper_probe_single_connector_modes,
>> +     .destroy = mdp5_wb_connector_destroy,
>> +     .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
>> +     .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
>> +};
>> +
>> +static int
>> +mdp5_wb_encoder_atomic_check(struct drm_encoder *encoder,
>> +             struct drm_crtc_state *crtc_state,
>> +             struct drm_connector_state *conn_state)
>> +{
>> +     struct msm_drm_private *priv = encoder->dev->dev_private;
>> +     struct mdp5_crtc_state *mdp5_cstate = to_mdp5_crtc_state(crtc_state);
>> +     struct mdp5_wb_connector *mdp5_wb = to_mdp5_wb_connector(
>> +             to_wb_connector(conn_state->connector));
>> +     struct drm_framebuffer *fb;
>> +     const struct msm_format *format;
>> +     const struct mdp_format *mdp_fmt;
>> +     struct drm_format_name_buf format_name;
>> +     int ret;
>> +
>> +     if (!conn_state->writeback_job || !conn_state->writeback_job->fb)
>> +             return 0;
>> +
>> +     fb = conn_state->writeback_job->fb;
>> +
>> +     DBG("wb[%u]: check writeback %ux%u@%s", mdp5_wb->id,
>> +             fb->width, fb->height,
>> +             drm_get_format_name(fb->format->format, &format_name));
>> +
>> +     format = mdp_get_format(priv->kms, fb->format->format);
>> +     if (!format) {
>> +             DBG("Invalid pixel format!");
>> +             return -EINVAL;
>> +     }
>> +
>> +     mdp_fmt = to_mdp_format(format);
>> +     if (MDP_FORMAT_IS_YUV(mdp_fmt)) {
>> +             switch (mdp_fmt->chroma_sample) {
>> +             case CHROMA_420:
>> +             case CHROMA_H2V1:
>> +                     /* supported */
>> +                     break;
>> +             case CHROMA_H1V2:
>> +             default:
>> +                     DBG("unsupported wb chroma samp=%d\n",
>> +                             mdp_fmt->chroma_sample);
>> +                     return -EINVAL;
>> +             }
>> +     }
>> +
>> +     /* TODO I think we would prefer to have proper prepare_fb()/cleanup_fb()
>> +      * vfuncs, as with plane..  Also, where to unprepare?
>> +      */
>> +     ret = msm_framebuffer_prepare(fb, priv->kms->aspace);
>> +     if (ret)
>> +             return ret;
>> +
>> +     mdp5_cstate->ctl = mdp5_wb->ctl;
>> +     mdp5_cstate->pipeline.intf = mdp5_wb->intf;
>> +     mdp5_cstate->defer_start = true;
>> +
>> +     return 0;
>> +}
>> +
>> +static void
>> +wb_csc_setup(struct mdp5_kms *mdp5_kms, u32 wb_id, struct csc_cfg *csc)
>> +{
>> +     uint32_t  i;
>> +     uint32_t *matrix;
>> +
>> +     if (unlikely(!csc))
>> +             return;
>> +
>> +     matrix = csc->matrix;
>> +     mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_0(wb_id),
>> +             MDP5_WB_CSC_MATRIX_COEFF_0_COEFF_11(matrix[0]) |
>> +             MDP5_WB_CSC_MATRIX_COEFF_0_COEFF_12(matrix[1]));
>> +     mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_1(wb_id),
>> +             MDP5_WB_CSC_MATRIX_COEFF_1_COEFF_13(matrix[2]) |
>> +             MDP5_WB_CSC_MATRIX_COEFF_1_COEFF_21(matrix[3]));
>> +     mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_2(wb_id),
>> +             MDP5_WB_CSC_MATRIX_COEFF_2_COEFF_22(matrix[4]) |
>> +             MDP5_WB_CSC_MATRIX_COEFF_2_COEFF_23(matrix[5]));
>> +     mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_3(wb_id),
>> +             MDP5_WB_CSC_MATRIX_COEFF_3_COEFF_31(matrix[6]) |
>> +             MDP5_WB_CSC_MATRIX_COEFF_3_COEFF_32(matrix[7]));
>> +     mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_4(wb_id),
>> +             MDP5_WB_CSC_MATRIX_COEFF_4_COEFF_33(matrix[8]));
>> +
>> +     for (i = 0; i < ARRAY_SIZE(csc->pre_bias); i++) {
>> +             uint32_t *pre_clamp = csc->pre_clamp;
>> +             uint32_t *post_clamp = csc->post_clamp;
>> +
>> +             mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_COMP_PRECLAMP(wb_id, i),
>> +                     MDP5_WB_CSC_COMP_PRECLAMP_REG_HIGH(pre_clamp[2*i+1]) |
>> +                     MDP5_WB_CSC_COMP_PRECLAMP_REG_LOW(pre_clamp[2*i]));
>> +
>> +             mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_COMP_POSTCLAMP(wb_id, i),
>> +                     MDP5_WB_CSC_COMP_POSTCLAMP_REG_HIGH(post_clamp[2*i+1]) |
>> +                     MDP5_WB_CSC_COMP_POSTCLAMP_REG_LOW(post_clamp[2*i]));
>> +
>> +             mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_COMP_PREBIAS(wb_id, i),
>> +                     MDP5_WB_CSC_COMP_PREBIAS_REG_VALUE(csc->pre_bias[i]));
>> +
>> +             mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_COMP_POSTBIAS(wb_id, i),
>> +                     MDP5_WB_CSC_COMP_POSTBIAS_REG_VALUE(csc->post_bias[i]));
>> +     }
>> +}
>> +
>> +static void
>> +mdp5_wb_connector_atomic_commit(struct drm_connector *connector,
>> +             struct drm_writeback_job *job)
>> +{
>> +     struct msm_drm_private *priv = connector->dev->dev_private;
>> +     struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(priv->kms));
>> +     struct drm_connector_state *conn_state = connector->state;
>> +     struct drm_writeback_connector *wb_conn = to_wb_connector(connector);
>> +     struct mdp5_crtc_state *mdp5_crtc_state =
>> +             to_mdp5_crtc_state(wb_conn->encoder.crtc->state);
>> +     struct mdp5_wb_connector *mdp5_wb = to_mdp5_wb_connector(wb_conn);
>> +     struct drm_framebuffer *fb = job->fb;
>> +     struct drm_format_name_buf format_name;
>> +     const struct mdp_format *fmt =
>> +             to_mdp_format(mdp_get_format(priv->kms, fb->format->format));
>> +     u32 ystride0, ystride1, outsize;
>> +     u32 dst_format, pattern, opmode = 0;
>> +
>> +     DBG("wb[%u]: kick writeback %ux%u@%s", mdp5_wb->id,
>> +             fb->width, fb->height,
>> +             drm_get_format_name(fb->format->format, &format_name));
>> +
>> +     /* queue job before anything that can trigger completion irq */
>> +     drm_writeback_queue_job(wb_conn, job);
>> +     conn_state->writeback_job = NULL;
>> +
>> +     mdp_irq_register(&mdp5_kms->base, &mdp5_wb->wb_done);
>> +
>> +     if (MDP_FORMAT_IS_YUV(fmt)) {
>> +             wb_csc_setup(mdp5_kms, mdp5_wb->id,
>> +                     mdp_get_default_csc_cfg(CSC_RGB2YUV));
>> +
>> +             opmode |= MDP5_WB_DST_OP_MODE_CSC_EN |
>> +                     MDP5_WB_DST_OP_MODE_CSC_SRC_DATA_FORMAT(DATA_FORMAT_RGB) |
>> +                     MDP5_WB_DST_OP_MODE_CSC_DST_DATA_FORMAT(DATA_FORMAT_YUV);
>> +
>> +             switch (fmt->chroma_sample) {
>> +             case CHROMA_420:
>> +             case CHROMA_H2V1:
>> +                     opmode |= MDP5_WB_DST_OP_MODE_CHROMA_DWN_SAMPLE_EN;
>> +                     break;
>> +             case CHROMA_H1V2:
>> +             default:
>> +                     WARN(1, "unsupported wb chroma samp=%d\n",
>> +                             fmt->chroma_sample);
>> +                     return;
>> +             }
>> +     }
>> +
>> +     dst_format = MDP5_WB_DST_FORMAT_DST_CHROMA_SAMP(fmt->chroma_sample) |
>> +             MDP5_WB_DST_FORMAT_WRITE_PLANES(fmt->fetch_type) |
>> +             MDP5_WB_DST_FORMAT_DSTC3_OUT(fmt->bpc_a) |
>> +             MDP5_WB_DST_FORMAT_DSTC2_OUT(fmt->bpc_r) |
>> +             MDP5_WB_DST_FORMAT_DSTC1_OUT(fmt->bpc_b) |
>> +             MDP5_WB_DST_FORMAT_DSTC0_OUT(fmt->bpc_g) |
>> +             COND(fmt->unpack_tight, MDP5_WB_DST_FORMAT_PACK_TIGHT) |
>> +             MDP5_WB_DST_FORMAT_PACK_COUNT(fmt->unpack_count - 1) |
>> +             MDP5_WB_DST_FORMAT_DST_BPP(fmt->cpp - 1);
>> +
>> +     if (fmt->bpc_a || fmt->alpha_enable) {
>> +             dst_format |= MDP5_WB_DST_FORMAT_DSTC3_EN;
>> +             if (!fmt->alpha_enable)
>> +                     dst_format |= MDP5_WB_DST_FORMAT_DST_ALPHA_X;
>> +     }
>> +
>> +     pattern = MDP5_WB_DST_PACK_PATTERN_ELEMENT3(fmt->unpack[3]) |
>> +             MDP5_WB_DST_PACK_PATTERN_ELEMENT2(fmt->unpack[2]) |
>> +             MDP5_WB_DST_PACK_PATTERN_ELEMENT1(fmt->unpack[1]) |
>> +             MDP5_WB_DST_PACK_PATTERN_ELEMENT0(fmt->unpack[0]);
>> +
>> +     ystride0 = MDP5_WB_DST_YSTRIDE0_DST0_YSTRIDE(fb->pitches[0]) |
>> +             MDP5_WB_DST_YSTRIDE0_DST1_YSTRIDE(fb->pitches[1]);
>> +     ystride1 = MDP5_WB_DST_YSTRIDE1_DST2_YSTRIDE(fb->pitches[2]) |
>> +             MDP5_WB_DST_YSTRIDE1_DST3_YSTRIDE(fb->pitches[3]);
>> +
>> +     /* get the output resolution from WB device */
>> +     outsize = MDP5_WB_OUT_SIZE_DST_H(fb->height) |
>> +             MDP5_WB_OUT_SIZE_DST_W(fb->width);
>> +
>> +     mdp5_write(mdp5_kms, REG_MDP5_WB_ALPHA_X_VALUE(mdp5_wb->id), 0xff);
>> +     mdp5_write(mdp5_kms, REG_MDP5_WB_DST_FORMAT(mdp5_wb->id), dst_format);
>> +     mdp5_write(mdp5_kms, REG_MDP5_WB_DST_OP_MODE(mdp5_wb->id), opmode);
>> +     mdp5_write(mdp5_kms, REG_MDP5_WB_DST_PACK_PATTERN(mdp5_wb->id), pattern);
>> +     mdp5_write(mdp5_kms, REG_MDP5_WB_DST_YSTRIDE0(mdp5_wb->id), ystride0);
>> +     mdp5_write(mdp5_kms, REG_MDP5_WB_DST_YSTRIDE1(mdp5_wb->id), ystride1);
>> +     mdp5_write(mdp5_kms, REG_MDP5_WB_OUT_SIZE(mdp5_wb->id), outsize);
>> +
>> +     mdp5_crtc_set_pipeline(wb_conn->encoder.crtc);
>> +
>> +     mdp5_write(mdp5_kms, REG_MDP5_WB_DST0_ADDR(mdp5_wb->id),
>> +             msm_framebuffer_iova(fb, priv->kms->aspace, 0));
>> +     mdp5_write(mdp5_kms, REG_MDP5_WB_DST1_ADDR(mdp5_wb->id),
>> +             msm_framebuffer_iova(fb, priv->kms->aspace, 1));
>> +     mdp5_write(mdp5_kms, REG_MDP5_WB_DST2_ADDR(mdp5_wb->id),
>> +             msm_framebuffer_iova(fb, priv->kms->aspace, 2));
>> +     mdp5_write(mdp5_kms, REG_MDP5_WB_DST3_ADDR(mdp5_wb->id),
>> +             msm_framebuffer_iova(fb, priv->kms->aspace, 3));
>> +
>> +     /* Notify ctl that wb buffer is ready to trigger start */
>> +     mdp5_ctl_commit(mdp5_wb->ctl, &mdp5_crtc_state->pipeline,
>> +             MDP5_CTL_FLUSH_WB, true);
>> +
>> +     mdp5_ctl_set_encoder_state(mdp5_wb->ctl,
>> +             &mdp5_crtc_state->pipeline, true);
>> +}
>> +
>> +static void mdp5_wb_done_irq(struct mdp_irq *irq, uint32_t irqstatus)
>> +{
>> +     struct mdp5_wb_connector *mdp5_wb =
>> +             container_of(irq, struct mdp5_wb_connector, wb_done);
>> +     struct mdp5_crtc_state *mdp5_crtc_state =
>> +             to_mdp5_crtc_state(mdp5_wb->base.encoder.crtc->state);
>> +     struct msm_drm_private *priv = mdp5_wb->base.base.dev->dev_private;
>> +
>> +     mdp_irq_unregister(to_mdp_kms(priv->kms), &mdp5_wb->wb_done);
>> +
>> +     mdp5_ctl_set_encoder_state(mdp5_wb->ctl,
>> +             &mdp5_crtc_state->pipeline, false);
>> +
>> +     drm_writeback_signal_completion(&mdp5_wb->base, 0);
>> +}
>> +
>> +static const struct drm_encoder_helper_funcs mdp5_wb_encoder_helper_funcs = {
>> +     .atomic_check = mdp5_wb_encoder_atomic_check,
>> +};
>> +
>> +struct drm_writeback_connector *
>> +mdp5_wb_connector_init(struct drm_device *dev, struct mdp5_ctl *ctl,
>> +             unsigned wb_id)
>> +{
>> +     struct drm_connector *connector = NULL;
>> +     struct mdp5_wb_connector *mdp5_wb;
>> +
>> +     mdp5_wb = kzalloc(sizeof(*mdp5_wb), GFP_KERNEL);
>> +     if (!mdp5_wb)
>> +             return ERR_PTR(-ENOMEM);
>> +
>> +     mdp5_wb->id = wb_id;
>> +     mdp5_wb->ctl = ctl;
>> +
>> +     /* construct a dummy intf for WB: */
>> +// TODO un-inline this (and also in interface_init())
>> +     mdp5_wb->intf = kzalloc(sizeof(*mdp5_wb->intf), GFP_KERNEL);
>> +     mdp5_wb->intf->num = -1;
>> +     mdp5_wb->intf->type = INTF_WB;
>> +     mdp5_wb->intf->mode = MDP5_INTF_WB_MODE_LINE;
>> +     mdp5_wb->intf->idx = -1;
>> +
>> +     mdp5_wb->wb_done.irq = mdp5_wb_done_irq;
>> +// TODO just register for all wb irq's until I figure out the mapping..
>> +     mdp5_wb->wb_done.irqmask = MDP5_IRQ_WB_0_DONE | MDP5_IRQ_WB_1_DONE | MDP5_IRQ_WB_2_DONE;
>> +
>> +     connector = &mdp5_wb->base.base;
>> +
>> +     drm_connector_helper_add(connector, &mdp5_wb_connector_helper_funcs);
>> +
>> +     mdp5_wb->nformats = mdp_get_formats(mdp5_wb->formats,
>> +             ARRAY_SIZE(mdp5_wb->formats), false);
>> +
>> +     drm_writeback_connector_init(dev,
>> +             &mdp5_wb->base,
>> +             &mdp5_wb_connector_funcs,
>> +             &mdp5_wb_encoder_helper_funcs,
>> +             mdp5_wb->formats,
>> +             mdp5_wb->nformats);
>> +
>> +     connector->interlace_allowed = 0;
>> +     connector->doublescan_allowed = 0;
>
> These are handled by drm_writeback_connector_init (and kzalloc)

hmm, not doublescan_allowed, although perhaps it should be.

BR,
-R

>
>> +
>> +     return &mdp5_wb->base;
>> +}
>> diff --git a/drivers/gpu/drm/msm/dsi/dsi_host.c b/drivers/gpu/drm/msm/dsi/dsi_host.c
>> index 7a03a9489708..422f524f7562 100644
>> --- a/drivers/gpu/drm/msm/dsi/dsi_host.c
>> +++ b/drivers/gpu/drm/msm/dsi/dsi_host.c
>> @@ -741,7 +741,7 @@ static void dsi_intr_ctrl(struct msm_dsi_host *msm_host, u32 mask, int enable)
>>       else
>>               intr &= ~mask;
>>
>> -     DBG("intr=%x enable=%d", intr, enable);
>> +     VERB("intr=%x enable=%d", intr, enable);
>>
>>       dsi_write(msm_host, REG_DSI_INTR_CTRL, intr);
>>       spin_unlock_irqrestore(&msm_host->intr_lock, flags);
>> @@ -1465,7 +1465,7 @@ static irqreturn_t dsi_host_irq(int irq, void *ptr)
>>       dsi_write(msm_host, REG_DSI_INTR_CTRL, isr);
>>       spin_unlock_irqrestore(&msm_host->intr_lock, flags);
>>
>> -     DBG("isr=0x%x, id=%d", isr, msm_host->id);
>> +     VERB("isr=0x%x, id=%d", isr, msm_host->id);
>>
>>       if (isr & DSI_IRQ_ERROR)
>>               dsi_error(msm_host);
>> --
>> 2.14.3
>>
>
> --
> Sean Paul, Software Engineer, Google / Chromium OS
_______________________________________________
Freedreno mailing list
Freedreno@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/freedreno

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [RFC 4/4] drm/msm/mdp5: writeback support
@ 2018-02-23 18:15           ` Rob Clark
  0 siblings, 0 replies; 41+ messages in thread
From: Rob Clark @ 2018-02-23 18:15 UTC (permalink / raw)
  To: Sean Paul
  Cc: dri-devel, freedreno, linux-arm-msm, Brian Starkey, Liviu Dudau,
	David Airlie, Archit Taneja, Daniel Vetter, Laurent Pinchart,
	Ville Syrjälä,
	Neil Armstrong, Sushmita Susheelendra, Linux Kernel Mailing List

On Fri, Feb 23, 2018 at 11:30 AM, Sean Paul <seanpaul@chromium.org> wrote:
> On Fri, Feb 23, 2018 at 08:17:54AM -0500, Rob Clark wrote:
>> In a way, based on the original writeback patch from Jilai Wang, but a
>> lot has shifted around since then.
>>
>> Signed-off-by: Rob Clark <robdclark@gmail.com>
>> ---
>>  drivers/gpu/drm/msm/Makefile              |   1 +
>>  drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c |  23 +-
>>  drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c  |  38 +++-
>>  drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.h  |   7 +-
>>  drivers/gpu/drm/msm/disp/mdp5/mdp5_wb.c   | 367 ++++++++++++++++++++++++++++++
>>  drivers/gpu/drm/msm/dsi/dsi_host.c        |   4 +-
>>  6 files changed, 431 insertions(+), 9 deletions(-)
>>  create mode 100644 drivers/gpu/drm/msm/disp/mdp5/mdp5_wb.c
>>
>> diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
>> index cd40c050b2d7..c9f50adef2db 100644
>> --- a/drivers/gpu/drm/msm/Makefile
>> +++ b/drivers/gpu/drm/msm/Makefile
>> @@ -45,6 +45,7 @@ msm-y := \
>>       disp/mdp5/mdp5_mixer.o \
>>       disp/mdp5/mdp5_plane.o \
>>       disp/mdp5/mdp5_smp.o \
>> +     disp/mdp5/mdp5_wb.o \
>>       msm_atomic.o \
>>       msm_debugfs.o \
>>       msm_drv.o \
>> diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c
>> index 9893e43ba6c5..b00ca88b741d 100644
>> --- a/drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c
>> +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c
>> @@ -484,7 +484,11 @@ static void mdp5_crtc_atomic_enable(struct drm_crtc *crtc,
>>       }
>>
>>       /* Restore vblank irq handling after power is enabled */
>> -     drm_crtc_vblank_on(crtc);
>> +// TODO we can't ->get_scanout_pos() for wb (since virtual intf)..
>> +// perhaps drm core should be clever enough not to drm_reset_vblank_timestamp()
>> +// for virtual encoders / writeback?
>> +     if (mdp5_cstate->pipeline.intf->type != INTF_WB)
>> +             drm_crtc_vblank_on(crtc);
>>
>>       mdp5_crtc_mode_set_nofb(crtc);
>>
>> @@ -518,7 +522,11 @@ int mdp5_crtc_setup_pipeline(struct drm_crtc *crtc,
>>               u32 caps;
>>               int ret;
>>
>> -             caps = MDP_LM_CAP_DISPLAY;
>> +             if (pipeline->intf->type == INTF_WB)
>> +                     caps = MDP_LM_CAP_WB;
>> +             else
>> +                     caps = MDP_LM_CAP_DISPLAY;
>> +
>>               if (need_right_mixer)
>>                       caps |= MDP_LM_CAP_PAIR;
>>
>> @@ -545,6 +553,7 @@ int mdp5_crtc_setup_pipeline(struct drm_crtc *crtc,
>>       mdp5_cstate->err_irqmask = intf2err(intf->num);
>>       mdp5_cstate->vblank_irqmask = intf2vblank(pipeline->mixer, intf);
>>
>> +// XXX should we be treating WB as cmd_mode??
>>       if ((intf->type == INTF_DSI) &&
>>           (intf->mode == MDP5_INTF_DSI_MODE_COMMAND)) {
>>               mdp5_cstate->pp_done_irqmask = lm2ppdone(pipeline->mixer);
>> @@ -639,8 +648,12 @@ static int mdp5_crtc_atomic_check(struct drm_crtc *crtc,
>>       }
>>
>>       /* bail out early if there aren't any planes */
>> -     if (!cnt)
>> -             return 0;
>> +     if (!cnt) {
>> +             if (!state->active)
>> +                     return 0;
>> +             dev_err(dev->dev, "%s has no planes!\n", crtc->name);
>> +             return -EINVAL;
>> +     }
>
> This seems unrelated?
>

hmm, yeah, kinda.  It was a case that I hit before working out all the
bugs in my kmscube writeback mode, but I guess isn't directly related
to wb.

>>
>>       hw_cfg = mdp5_cfg_get_hw_config(mdp5_kms->cfg);
>>
>> @@ -1160,7 +1173,7 @@ void mdp5_crtc_wait_for_commit_done(struct drm_crtc *crtc)
>>
>>       if (mdp5_cstate->cmd_mode)
>>               mdp5_crtc_wait_for_pp_done(crtc);
>> -     else
>> +     else if (mdp5_cstate->pipeline.intf->type != INTF_WB)
>>               mdp5_crtc_wait_for_flush_done(crtc);
>>  }
>>
>> diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c
>> index 1f44d8f15ce1..239010905637 100644
>> --- a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c
>> +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c
>> @@ -427,7 +427,8 @@ static int modeset_init(struct mdp5_kms *mdp5_kms)
>>        * the MDP5 interfaces) than the number of layer mixers present in HW,
>>        * but let's be safe here anyway
>>        */
>> -     num_crtcs = min(priv->num_encoders, mdp5_kms->num_hwmixers);
>> +     num_crtcs = min(priv->num_encoders + hw_cfg->wb.count,
>> +                     mdp5_kms->num_hwmixers);
>>
>>       /*
>>        * Construct planes equaling the number of hw pipes, and CRTCs for the
>> @@ -482,6 +483,33 @@ static int modeset_init(struct mdp5_kms *mdp5_kms)
>>               encoder->possible_crtcs = (1 << priv->num_crtcs) - 1;
>>       }
>>
>> +     /*
>> +      * Lastly, construct writeback connectors.
>> +      */
>> +     for (i = 0; i < hw_cfg->wb.count; i++) {
>> +             struct drm_writeback_connector *wb_conn;
>> +             struct mdp5_ctl *ctl;
>> +
>> +             ctl = mdp5_ctlm_request(mdp5_kms->ctlm, -1);
>> +             if (!ctl) {
>> +                     dev_err(dev->dev,
>> +                             "failed to allocate ctl for writeback %d\n", i);
>> +                     continue;
>> +             }
>> +
>> +             wb_conn = mdp5_wb_connector_init(dev, ctl,
>> +                             hw_cfg->wb.instances[i].id);
>> +             if (IS_ERR(wb_conn)) {
>> +                     ret = PTR_ERR(wb_conn);
>> +                     dev_err(dev->dev,
>> +                             "failed to construct writeback connector %d (%d)\n",
>> +                             i, ret);
>> +                     goto fail;
>> +             }
>> +
>> +             wb_conn->encoder.possible_crtcs = (1 << priv->num_crtcs) - 1;
>> +     }
>> +
>>       return 0;
>>
>>  fail:
>> @@ -555,6 +583,10 @@ static bool mdp5_get_scanoutpos(struct drm_device *dev, unsigned int pipe,
>>               return false;
>>       }
>>
>> +     /* unsupported for writeback: */
>> +     if (encoder->encoder_type == DRM_MODE_ENCODER_VIRTUAL)
>> +             return false;
>> +
>>       vsw = mode->crtc_vsync_end - mode->crtc_vsync_start;
>>       vbp = mode->crtc_vtotal - mode->crtc_vsync_end;
>>
>> @@ -610,6 +642,10 @@ static u32 mdp5_get_vblank_counter(struct drm_device *dev, unsigned int pipe)
>>       if (!encoder)
>>               return 0;
>>
>> +     /* unsupported for writeback: */
>> +     if (encoder->encoder_type == DRM_MODE_ENCODER_VIRTUAL)
>> +             return 0;
>> +
>>       return mdp5_encoder_get_framecount(encoder);
>>  }
>>
>> diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.h b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.h
>> index 425a03d213e5..be0f93ef33e1 100644
>> --- a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.h
>> +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.h
>> @@ -18,6 +18,8 @@
>>  #ifndef __MDP5_KMS_H__
>>  #define __MDP5_KMS_H__
>>
>> +#include <drm/drm_writeback.h>
>> +
>>  #include "msm_drv.h"
>>  #include "msm_kms.h"
>>  #include "disp/mdp_kms.h"
>> @@ -251,7 +253,7 @@ static inline uint32_t intf2vblank(struct mdp5_hw_mixer *mixer,
>>               return MDP5_IRQ_PING_PONG_0_RD_PTR << mixer->pp;
>>
>>       if (intf->type == INTF_WB)
>> -             return MDP5_IRQ_WB_2_DONE;
>> +             return MDP5_IRQ_WB_2_DONE | MDP5_IRQ_WB_0_DONE | MDP5_IRQ_WB_1_DONE;
>>
>>       switch (intf->num) {
>>       case 0:  return MDP5_IRQ_INTF0_VSYNC;
>> @@ -330,4 +332,7 @@ static inline int mdp5_cmd_encoder_set_split_display(
>>  }
>>  #endif
>>
>> +struct drm_writeback_connector *mdp5_wb_connector_init(struct drm_device *dev,
>> +             struct mdp5_ctl *ctl, unsigned wb_id);
>> +
>>  #endif /* __MDP5_KMS_H__ */
>> diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_wb.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_wb.c
>> new file mode 100644
>> index 000000000000..3dabd0a1aa8b
>> --- /dev/null
>> +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_wb.c
>> @@ -0,0 +1,367 @@
>> +/*
>> + * Copyright (C) 2018 Red Hat
>> + * Author: Rob Clark <robdclark@gmail.com>
>> + *
>> + * This program is free software; you can redistribute it and/or modify it
>> + * under the terms of the GNU General Public License version 2 as published by
>> + * the Free Software Foundation.
>> + *
>> + * This program is distributed in the hope that it will be useful, but WITHOUT
>> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
>> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
>> + * more details.
>> + *
>> + * You should have received a copy of the GNU General Public License along with
>> + * this program.  If not, see <http://www.gnu.org/licenses/>.
>
> SPDX license
>
>> + */
>> +
>> +#include "mdp5_kms.h"
>> +
>> +/*
>> + * Writeback connector/encoder implementation:
>> + */
>> +
>> +struct mdp5_wb_connector {
>> +     struct drm_writeback_connector base;
>> +
>> +     u32 nformats;
>> +     u32 formats[32];
>> +
>> +     unsigned id;
>> +     struct mdp5_ctl *ctl;
>> +     struct mdp5_interface *intf;
>> +
>> +     struct mdp_irq wb_done;
>> +};
>> +#define to_mdp5_wb_connector(x) container_of(x, struct mdp5_wb_connector, base)
>> +
>> +
>> +static void mdp5_wb_connector_atomic_commit(struct drm_connector *connector,
>> +             struct drm_writeback_job *job);
>> +
>> +static int mdp5_wb_connector_get_modes(struct drm_connector *connector)
>> +{
>> +     struct drm_device *dev = connector->dev;
>> +
>> +     return drm_add_modes_noedid(connector, dev->mode_config.max_width,
>> +                     dev->mode_config.max_height);
>
> I guess the plan is to let userspace provide user-defined modes for wb? Is it
> ever useful to have this token mode?

/me shrugs

I just copied this from the malidp wb patch.  This actually does add
multiple modes (all the dmt modes that fit within max width/height).
Not sure if that is useful or not.

>
>> +}
>> +
>> +static enum drm_mode_status
>> +mdp5_wb_connector_mode_valid(struct drm_connector *connector,
>> +             struct drm_display_mode *mode)
>> +{
>> +     struct drm_device *dev = connector->dev;
>> +     struct drm_mode_config *mode_config = &dev->mode_config;
>> +     int w = mode->hdisplay, h = mode->vdisplay;
>> +
>> +     if ((w < mode_config->min_width) || (w > mode_config->max_width))
>> +             return MODE_BAD_HVALUE;
>> +
>> +     if ((h < mode_config->min_height) || (h > mode_config->max_height))
>> +             return MODE_BAD_VVALUE;
>> +
>> +     return MODE_OK;
>> +}
>
> Might be useful for these to migrate into drm_writeback_connector.c as helpers.

Even the _get_modes(), tbh, since I think all of these ended up the
same as malidp..

>
>> +
>> +const struct drm_connector_helper_funcs mdp5_wb_connector_helper_funcs = {
>> +     .get_modes = mdp5_wb_connector_get_modes,
>> +     .mode_valid = mdp5_wb_connector_mode_valid,
>> +     .atomic_commit = mdp5_wb_connector_atomic_commit,
>> +};
>> +
>> +static enum drm_connector_status
>> +mdp5_wb_connector_detect(struct drm_connector *connector, bool force)
>> +{
>> +     return connector_status_disconnected;
>> +}
>
> This should be a helper as well.
>
>> +
>> +static void mdp5_wb_connector_destroy(struct drm_connector *connector)
>> +{
>> +     drm_connector_cleanup(connector);
>> +}
>
> Just use drm_connector_cleanup directly below
>

actually, I seem to have lost a fixup, there is some memory to free in
_destroy()

And I suspect we need a drm_writeback_connector_cleanup()..

>> +
>> +static const struct drm_connector_funcs mdp5_wb_connector_funcs = {
>> +     .reset = drm_atomic_helper_connector_reset,
>> +     .detect = mdp5_wb_connector_detect,
>> +     .fill_modes = drm_helper_probe_single_connector_modes,
>> +     .destroy = mdp5_wb_connector_destroy,
>> +     .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
>> +     .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
>> +};
>> +
>> +static int
>> +mdp5_wb_encoder_atomic_check(struct drm_encoder *encoder,
>> +             struct drm_crtc_state *crtc_state,
>> +             struct drm_connector_state *conn_state)
>> +{
>> +     struct msm_drm_private *priv = encoder->dev->dev_private;
>> +     struct mdp5_crtc_state *mdp5_cstate = to_mdp5_crtc_state(crtc_state);
>> +     struct mdp5_wb_connector *mdp5_wb = to_mdp5_wb_connector(
>> +             to_wb_connector(conn_state->connector));
>> +     struct drm_framebuffer *fb;
>> +     const struct msm_format *format;
>> +     const struct mdp_format *mdp_fmt;
>> +     struct drm_format_name_buf format_name;
>> +     int ret;
>> +
>> +     if (!conn_state->writeback_job || !conn_state->writeback_job->fb)
>> +             return 0;
>> +
>> +     fb = conn_state->writeback_job->fb;
>> +
>> +     DBG("wb[%u]: check writeback %ux%u@%s", mdp5_wb->id,
>> +             fb->width, fb->height,
>> +             drm_get_format_name(fb->format->format, &format_name));
>> +
>> +     format = mdp_get_format(priv->kms, fb->format->format);
>> +     if (!format) {
>> +             DBG("Invalid pixel format!");
>> +             return -EINVAL;
>> +     }
>> +
>> +     mdp_fmt = to_mdp_format(format);
>> +     if (MDP_FORMAT_IS_YUV(mdp_fmt)) {
>> +             switch (mdp_fmt->chroma_sample) {
>> +             case CHROMA_420:
>> +             case CHROMA_H2V1:
>> +                     /* supported */
>> +                     break;
>> +             case CHROMA_H1V2:
>> +             default:
>> +                     DBG("unsupported wb chroma samp=%d\n",
>> +                             mdp_fmt->chroma_sample);
>> +                     return -EINVAL;
>> +             }
>> +     }
>> +
>> +     /* TODO I think we would prefer to have proper prepare_fb()/cleanup_fb()
>> +      * vfuncs, as with plane..  Also, where to unprepare?
>> +      */
>> +     ret = msm_framebuffer_prepare(fb, priv->kms->aspace);
>> +     if (ret)
>> +             return ret;
>> +
>> +     mdp5_cstate->ctl = mdp5_wb->ctl;
>> +     mdp5_cstate->pipeline.intf = mdp5_wb->intf;
>> +     mdp5_cstate->defer_start = true;
>> +
>> +     return 0;
>> +}
>> +
>> +static void
>> +wb_csc_setup(struct mdp5_kms *mdp5_kms, u32 wb_id, struct csc_cfg *csc)
>> +{
>> +     uint32_t  i;
>> +     uint32_t *matrix;
>> +
>> +     if (unlikely(!csc))
>> +             return;
>> +
>> +     matrix = csc->matrix;
>> +     mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_0(wb_id),
>> +             MDP5_WB_CSC_MATRIX_COEFF_0_COEFF_11(matrix[0]) |
>> +             MDP5_WB_CSC_MATRIX_COEFF_0_COEFF_12(matrix[1]));
>> +     mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_1(wb_id),
>> +             MDP5_WB_CSC_MATRIX_COEFF_1_COEFF_13(matrix[2]) |
>> +             MDP5_WB_CSC_MATRIX_COEFF_1_COEFF_21(matrix[3]));
>> +     mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_2(wb_id),
>> +             MDP5_WB_CSC_MATRIX_COEFF_2_COEFF_22(matrix[4]) |
>> +             MDP5_WB_CSC_MATRIX_COEFF_2_COEFF_23(matrix[5]));
>> +     mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_3(wb_id),
>> +             MDP5_WB_CSC_MATRIX_COEFF_3_COEFF_31(matrix[6]) |
>> +             MDP5_WB_CSC_MATRIX_COEFF_3_COEFF_32(matrix[7]));
>> +     mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_4(wb_id),
>> +             MDP5_WB_CSC_MATRIX_COEFF_4_COEFF_33(matrix[8]));
>> +
>> +     for (i = 0; i < ARRAY_SIZE(csc->pre_bias); i++) {
>> +             uint32_t *pre_clamp = csc->pre_clamp;
>> +             uint32_t *post_clamp = csc->post_clamp;
>> +
>> +             mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_COMP_PRECLAMP(wb_id, i),
>> +                     MDP5_WB_CSC_COMP_PRECLAMP_REG_HIGH(pre_clamp[2*i+1]) |
>> +                     MDP5_WB_CSC_COMP_PRECLAMP_REG_LOW(pre_clamp[2*i]));
>> +
>> +             mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_COMP_POSTCLAMP(wb_id, i),
>> +                     MDP5_WB_CSC_COMP_POSTCLAMP_REG_HIGH(post_clamp[2*i+1]) |
>> +                     MDP5_WB_CSC_COMP_POSTCLAMP_REG_LOW(post_clamp[2*i]));
>> +
>> +             mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_COMP_PREBIAS(wb_id, i),
>> +                     MDP5_WB_CSC_COMP_PREBIAS_REG_VALUE(csc->pre_bias[i]));
>> +
>> +             mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_COMP_POSTBIAS(wb_id, i),
>> +                     MDP5_WB_CSC_COMP_POSTBIAS_REG_VALUE(csc->post_bias[i]));
>> +     }
>> +}
>> +
>> +static void
>> +mdp5_wb_connector_atomic_commit(struct drm_connector *connector,
>> +             struct drm_writeback_job *job)
>> +{
>> +     struct msm_drm_private *priv = connector->dev->dev_private;
>> +     struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(priv->kms));
>> +     struct drm_connector_state *conn_state = connector->state;
>> +     struct drm_writeback_connector *wb_conn = to_wb_connector(connector);
>> +     struct mdp5_crtc_state *mdp5_crtc_state =
>> +             to_mdp5_crtc_state(wb_conn->encoder.crtc->state);
>> +     struct mdp5_wb_connector *mdp5_wb = to_mdp5_wb_connector(wb_conn);
>> +     struct drm_framebuffer *fb = job->fb;
>> +     struct drm_format_name_buf format_name;
>> +     const struct mdp_format *fmt =
>> +             to_mdp_format(mdp_get_format(priv->kms, fb->format->format));
>> +     u32 ystride0, ystride1, outsize;
>> +     u32 dst_format, pattern, opmode = 0;
>> +
>> +     DBG("wb[%u]: kick writeback %ux%u@%s", mdp5_wb->id,
>> +             fb->width, fb->height,
>> +             drm_get_format_name(fb->format->format, &format_name));
>> +
>> +     /* queue job before anything that can trigger completion irq */
>> +     drm_writeback_queue_job(wb_conn, job);
>> +     conn_state->writeback_job = NULL;
>> +
>> +     mdp_irq_register(&mdp5_kms->base, &mdp5_wb->wb_done);
>> +
>> +     if (MDP_FORMAT_IS_YUV(fmt)) {
>> +             wb_csc_setup(mdp5_kms, mdp5_wb->id,
>> +                     mdp_get_default_csc_cfg(CSC_RGB2YUV));
>> +
>> +             opmode |= MDP5_WB_DST_OP_MODE_CSC_EN |
>> +                     MDP5_WB_DST_OP_MODE_CSC_SRC_DATA_FORMAT(DATA_FORMAT_RGB) |
>> +                     MDP5_WB_DST_OP_MODE_CSC_DST_DATA_FORMAT(DATA_FORMAT_YUV);
>> +
>> +             switch (fmt->chroma_sample) {
>> +             case CHROMA_420:
>> +             case CHROMA_H2V1:
>> +                     opmode |= MDP5_WB_DST_OP_MODE_CHROMA_DWN_SAMPLE_EN;
>> +                     break;
>> +             case CHROMA_H1V2:
>> +             default:
>> +                     WARN(1, "unsupported wb chroma samp=%d\n",
>> +                             fmt->chroma_sample);
>> +                     return;
>> +             }
>> +     }
>> +
>> +     dst_format = MDP5_WB_DST_FORMAT_DST_CHROMA_SAMP(fmt->chroma_sample) |
>> +             MDP5_WB_DST_FORMAT_WRITE_PLANES(fmt->fetch_type) |
>> +             MDP5_WB_DST_FORMAT_DSTC3_OUT(fmt->bpc_a) |
>> +             MDP5_WB_DST_FORMAT_DSTC2_OUT(fmt->bpc_r) |
>> +             MDP5_WB_DST_FORMAT_DSTC1_OUT(fmt->bpc_b) |
>> +             MDP5_WB_DST_FORMAT_DSTC0_OUT(fmt->bpc_g) |
>> +             COND(fmt->unpack_tight, MDP5_WB_DST_FORMAT_PACK_TIGHT) |
>> +             MDP5_WB_DST_FORMAT_PACK_COUNT(fmt->unpack_count - 1) |
>> +             MDP5_WB_DST_FORMAT_DST_BPP(fmt->cpp - 1);
>> +
>> +     if (fmt->bpc_a || fmt->alpha_enable) {
>> +             dst_format |= MDP5_WB_DST_FORMAT_DSTC3_EN;
>> +             if (!fmt->alpha_enable)
>> +                     dst_format |= MDP5_WB_DST_FORMAT_DST_ALPHA_X;
>> +     }
>> +
>> +     pattern = MDP5_WB_DST_PACK_PATTERN_ELEMENT3(fmt->unpack[3]) |
>> +             MDP5_WB_DST_PACK_PATTERN_ELEMENT2(fmt->unpack[2]) |
>> +             MDP5_WB_DST_PACK_PATTERN_ELEMENT1(fmt->unpack[1]) |
>> +             MDP5_WB_DST_PACK_PATTERN_ELEMENT0(fmt->unpack[0]);
>> +
>> +     ystride0 = MDP5_WB_DST_YSTRIDE0_DST0_YSTRIDE(fb->pitches[0]) |
>> +             MDP5_WB_DST_YSTRIDE0_DST1_YSTRIDE(fb->pitches[1]);
>> +     ystride1 = MDP5_WB_DST_YSTRIDE1_DST2_YSTRIDE(fb->pitches[2]) |
>> +             MDP5_WB_DST_YSTRIDE1_DST3_YSTRIDE(fb->pitches[3]);
>> +
>> +     /* get the output resolution from WB device */
>> +     outsize = MDP5_WB_OUT_SIZE_DST_H(fb->height) |
>> +             MDP5_WB_OUT_SIZE_DST_W(fb->width);
>> +
>> +     mdp5_write(mdp5_kms, REG_MDP5_WB_ALPHA_X_VALUE(mdp5_wb->id), 0xff);
>> +     mdp5_write(mdp5_kms, REG_MDP5_WB_DST_FORMAT(mdp5_wb->id), dst_format);
>> +     mdp5_write(mdp5_kms, REG_MDP5_WB_DST_OP_MODE(mdp5_wb->id), opmode);
>> +     mdp5_write(mdp5_kms, REG_MDP5_WB_DST_PACK_PATTERN(mdp5_wb->id), pattern);
>> +     mdp5_write(mdp5_kms, REG_MDP5_WB_DST_YSTRIDE0(mdp5_wb->id), ystride0);
>> +     mdp5_write(mdp5_kms, REG_MDP5_WB_DST_YSTRIDE1(mdp5_wb->id), ystride1);
>> +     mdp5_write(mdp5_kms, REG_MDP5_WB_OUT_SIZE(mdp5_wb->id), outsize);
>> +
>> +     mdp5_crtc_set_pipeline(wb_conn->encoder.crtc);
>> +
>> +     mdp5_write(mdp5_kms, REG_MDP5_WB_DST0_ADDR(mdp5_wb->id),
>> +             msm_framebuffer_iova(fb, priv->kms->aspace, 0));
>> +     mdp5_write(mdp5_kms, REG_MDP5_WB_DST1_ADDR(mdp5_wb->id),
>> +             msm_framebuffer_iova(fb, priv->kms->aspace, 1));
>> +     mdp5_write(mdp5_kms, REG_MDP5_WB_DST2_ADDR(mdp5_wb->id),
>> +             msm_framebuffer_iova(fb, priv->kms->aspace, 2));
>> +     mdp5_write(mdp5_kms, REG_MDP5_WB_DST3_ADDR(mdp5_wb->id),
>> +             msm_framebuffer_iova(fb, priv->kms->aspace, 3));
>> +
>> +     /* Notify ctl that wb buffer is ready to trigger start */
>> +     mdp5_ctl_commit(mdp5_wb->ctl, &mdp5_crtc_state->pipeline,
>> +             MDP5_CTL_FLUSH_WB, true);
>> +
>> +     mdp5_ctl_set_encoder_state(mdp5_wb->ctl,
>> +             &mdp5_crtc_state->pipeline, true);
>> +}
>> +
>> +static void mdp5_wb_done_irq(struct mdp_irq *irq, uint32_t irqstatus)
>> +{
>> +     struct mdp5_wb_connector *mdp5_wb =
>> +             container_of(irq, struct mdp5_wb_connector, wb_done);
>> +     struct mdp5_crtc_state *mdp5_crtc_state =
>> +             to_mdp5_crtc_state(mdp5_wb->base.encoder.crtc->state);
>> +     struct msm_drm_private *priv = mdp5_wb->base.base.dev->dev_private;
>> +
>> +     mdp_irq_unregister(to_mdp_kms(priv->kms), &mdp5_wb->wb_done);
>> +
>> +     mdp5_ctl_set_encoder_state(mdp5_wb->ctl,
>> +             &mdp5_crtc_state->pipeline, false);
>> +
>> +     drm_writeback_signal_completion(&mdp5_wb->base, 0);
>> +}
>> +
>> +static const struct drm_encoder_helper_funcs mdp5_wb_encoder_helper_funcs = {
>> +     .atomic_check = mdp5_wb_encoder_atomic_check,
>> +};
>> +
>> +struct drm_writeback_connector *
>> +mdp5_wb_connector_init(struct drm_device *dev, struct mdp5_ctl *ctl,
>> +             unsigned wb_id)
>> +{
>> +     struct drm_connector *connector = NULL;
>> +     struct mdp5_wb_connector *mdp5_wb;
>> +
>> +     mdp5_wb = kzalloc(sizeof(*mdp5_wb), GFP_KERNEL);
>> +     if (!mdp5_wb)
>> +             return ERR_PTR(-ENOMEM);
>> +
>> +     mdp5_wb->id = wb_id;
>> +     mdp5_wb->ctl = ctl;
>> +
>> +     /* construct a dummy intf for WB: */
>> +// TODO un-inline this (and also in interface_init())
>> +     mdp5_wb->intf = kzalloc(sizeof(*mdp5_wb->intf), GFP_KERNEL);
>> +     mdp5_wb->intf->num = -1;
>> +     mdp5_wb->intf->type = INTF_WB;
>> +     mdp5_wb->intf->mode = MDP5_INTF_WB_MODE_LINE;
>> +     mdp5_wb->intf->idx = -1;
>> +
>> +     mdp5_wb->wb_done.irq = mdp5_wb_done_irq;
>> +// TODO just register for all wb irq's until I figure out the mapping..
>> +     mdp5_wb->wb_done.irqmask = MDP5_IRQ_WB_0_DONE | MDP5_IRQ_WB_1_DONE | MDP5_IRQ_WB_2_DONE;
>> +
>> +     connector = &mdp5_wb->base.base;
>> +
>> +     drm_connector_helper_add(connector, &mdp5_wb_connector_helper_funcs);
>> +
>> +     mdp5_wb->nformats = mdp_get_formats(mdp5_wb->formats,
>> +             ARRAY_SIZE(mdp5_wb->formats), false);
>> +
>> +     drm_writeback_connector_init(dev,
>> +             &mdp5_wb->base,
>> +             &mdp5_wb_connector_funcs,
>> +             &mdp5_wb_encoder_helper_funcs,
>> +             mdp5_wb->formats,
>> +             mdp5_wb->nformats);
>> +
>> +     connector->interlace_allowed = 0;
>> +     connector->doublescan_allowed = 0;
>
> These are handled by drm_writeback_connector_init (and kzalloc)

hmm, not doublescan_allowed, although perhaps it should be.

BR,
-R

>
>> +
>> +     return &mdp5_wb->base;
>> +}
>> diff --git a/drivers/gpu/drm/msm/dsi/dsi_host.c b/drivers/gpu/drm/msm/dsi/dsi_host.c
>> index 7a03a9489708..422f524f7562 100644
>> --- a/drivers/gpu/drm/msm/dsi/dsi_host.c
>> +++ b/drivers/gpu/drm/msm/dsi/dsi_host.c
>> @@ -741,7 +741,7 @@ static void dsi_intr_ctrl(struct msm_dsi_host *msm_host, u32 mask, int enable)
>>       else
>>               intr &= ~mask;
>>
>> -     DBG("intr=%x enable=%d", intr, enable);
>> +     VERB("intr=%x enable=%d", intr, enable);
>>
>>       dsi_write(msm_host, REG_DSI_INTR_CTRL, intr);
>>       spin_unlock_irqrestore(&msm_host->intr_lock, flags);
>> @@ -1465,7 +1465,7 @@ static irqreturn_t dsi_host_irq(int irq, void *ptr)
>>       dsi_write(msm_host, REG_DSI_INTR_CTRL, isr);
>>       spin_unlock_irqrestore(&msm_host->intr_lock, flags);
>>
>> -     DBG("isr=0x%x, id=%d", isr, msm_host->id);
>> +     VERB("isr=0x%x, id=%d", isr, msm_host->id);
>>
>>       if (isr & DSI_IRQ_ERROR)
>>               dsi_error(msm_host);
>> --
>> 2.14.3
>>
>
> --
> Sean Paul, Software Engineer, Google / Chromium OS

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [RFC 4/4] drm/msm/mdp5: writeback support
  2018-02-23 18:15           ` Rob Clark
@ 2018-02-26 15:41               ` Sean Paul
  -1 siblings, 0 replies; 41+ messages in thread
From: Sean Paul @ 2018-02-26 15:41 UTC (permalink / raw)
  To: Rob Clark
  Cc: Archit Taneja, Laurent Pinchart, Neil Armstrong, David Airlie,
	linux-arm-msm, Liviu Dudau, Linux Kernel Mailing List, dri-devel,
	Sushmita Susheelendra, Sean Paul, Daniel Vetter, freedreno,
	Brian Starkey, Ville Syrjälä

On Fri, Feb 23, 2018 at 01:15:54PM -0500, Rob Clark wrote:
> On Fri, Feb 23, 2018 at 11:30 AM, Sean Paul <seanpaul@chromium.org> wrote:
> > On Fri, Feb 23, 2018 at 08:17:54AM -0500, Rob Clark wrote:
> >> In a way, based on the original writeback patch from Jilai Wang, but a
> >> lot has shifted around since then.
> >>
> >> Signed-off-by: Rob Clark <robdclark@gmail.com>
> >> ---
> >>  drivers/gpu/drm/msm/Makefile              |   1 +
> >>  drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c |  23 +-
> >>  drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c  |  38 +++-
> >>  drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.h  |   7 +-
> >>  drivers/gpu/drm/msm/disp/mdp5/mdp5_wb.c   | 367 ++++++++++++++++++++++++++++++
> >>  drivers/gpu/drm/msm/dsi/dsi_host.c        |   4 +-
> >>  6 files changed, 431 insertions(+), 9 deletions(-)
> >>  create mode 100644 drivers/gpu/drm/msm/disp/mdp5/mdp5_wb.c
> >>
> >> diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
> >> index cd40c050b2d7..c9f50adef2db 100644
> >> --- a/drivers/gpu/drm/msm/Makefile
> >> +++ b/drivers/gpu/drm/msm/Makefile
> >> @@ -45,6 +45,7 @@ msm-y := \
> >>       disp/mdp5/mdp5_mixer.o \
> >>       disp/mdp5/mdp5_plane.o \
> >>       disp/mdp5/mdp5_smp.o \
> >> +     disp/mdp5/mdp5_wb.o \
> >>       msm_atomic.o \
> >>       msm_debugfs.o \
> >>       msm_drv.o \
> >> diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c
> >> index 9893e43ba6c5..b00ca88b741d 100644
> >> --- a/drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c
> >> +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c
> >> @@ -484,7 +484,11 @@ static void mdp5_crtc_atomic_enable(struct drm_crtc *crtc,
> >>       }
> >>
> >>       /* Restore vblank irq handling after power is enabled */
> >> -     drm_crtc_vblank_on(crtc);
> >> +// TODO we can't ->get_scanout_pos() for wb (since virtual intf)..
> >> +// perhaps drm core should be clever enough not to drm_reset_vblank_timestamp()
> >> +// for virtual encoders / writeback?
> >> +     if (mdp5_cstate->pipeline.intf->type != INTF_WB)
> >> +             drm_crtc_vblank_on(crtc);
> >>
> >>       mdp5_crtc_mode_set_nofb(crtc);
> >>
> >> @@ -518,7 +522,11 @@ int mdp5_crtc_setup_pipeline(struct drm_crtc *crtc,
> >>               u32 caps;
> >>               int ret;
> >>
> >> -             caps = MDP_LM_CAP_DISPLAY;
> >> +             if (pipeline->intf->type == INTF_WB)
> >> +                     caps = MDP_LM_CAP_WB;
> >> +             else
> >> +                     caps = MDP_LM_CAP_DISPLAY;
> >> +
> >>               if (need_right_mixer)
> >>                       caps |= MDP_LM_CAP_PAIR;
> >>
> >> @@ -545,6 +553,7 @@ int mdp5_crtc_setup_pipeline(struct drm_crtc *crtc,
> >>       mdp5_cstate->err_irqmask = intf2err(intf->num);
> >>       mdp5_cstate->vblank_irqmask = intf2vblank(pipeline->mixer, intf);
> >>
> >> +// XXX should we be treating WB as cmd_mode??
> >>       if ((intf->type == INTF_DSI) &&
> >>           (intf->mode == MDP5_INTF_DSI_MODE_COMMAND)) {
> >>               mdp5_cstate->pp_done_irqmask = lm2ppdone(pipeline->mixer);
> >> @@ -639,8 +648,12 @@ static int mdp5_crtc_atomic_check(struct drm_crtc *crtc,
> >>       }
> >>
> >>       /* bail out early if there aren't any planes */
> >> -     if (!cnt)
> >> -             return 0;
> >> +     if (!cnt) {
> >> +             if (!state->active)
> >> +                     return 0;
> >> +             dev_err(dev->dev, "%s has no planes!\n", crtc->name);
> >> +             return -EINVAL;
> >> +     }
> >
> > This seems unrelated?
> >
> 
> hmm, yeah, kinda.  It was a case that I hit before working out all the
> bugs in my kmscube writeback mode, but I guess isn't directly related
> to wb.
> 
> >>
> >>       hw_cfg = mdp5_cfg_get_hw_config(mdp5_kms->cfg);
> >>
> >> @@ -1160,7 +1173,7 @@ void mdp5_crtc_wait_for_commit_done(struct drm_crtc *crtc)
> >>
> >>       if (mdp5_cstate->cmd_mode)
> >>               mdp5_crtc_wait_for_pp_done(crtc);
> >> -     else
> >> +     else if (mdp5_cstate->pipeline.intf->type != INTF_WB)
> >>               mdp5_crtc_wait_for_flush_done(crtc);
> >>  }
> >>
> >> diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c
> >> index 1f44d8f15ce1..239010905637 100644
> >> --- a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c
> >> +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c
> >> @@ -427,7 +427,8 @@ static int modeset_init(struct mdp5_kms *mdp5_kms)
> >>        * the MDP5 interfaces) than the number of layer mixers present in HW,
> >>        * but let's be safe here anyway
> >>        */
> >> -     num_crtcs = min(priv->num_encoders, mdp5_kms->num_hwmixers);
> >> +     num_crtcs = min(priv->num_encoders + hw_cfg->wb.count,
> >> +                     mdp5_kms->num_hwmixers);
> >>
> >>       /*
> >>        * Construct planes equaling the number of hw pipes, and CRTCs for the
> >> @@ -482,6 +483,33 @@ static int modeset_init(struct mdp5_kms *mdp5_kms)
> >>               encoder->possible_crtcs = (1 << priv->num_crtcs) - 1;
> >>       }
> >>
> >> +     /*
> >> +      * Lastly, construct writeback connectors.
> >> +      */
> >> +     for (i = 0; i < hw_cfg->wb.count; i++) {
> >> +             struct drm_writeback_connector *wb_conn;
> >> +             struct mdp5_ctl *ctl;
> >> +
> >> +             ctl = mdp5_ctlm_request(mdp5_kms->ctlm, -1);
> >> +             if (!ctl) {
> >> +                     dev_err(dev->dev,
> >> +                             "failed to allocate ctl for writeback %d\n", i);
> >> +                     continue;
> >> +             }
> >> +
> >> +             wb_conn = mdp5_wb_connector_init(dev, ctl,
> >> +                             hw_cfg->wb.instances[i].id);
> >> +             if (IS_ERR(wb_conn)) {
> >> +                     ret = PTR_ERR(wb_conn);
> >> +                     dev_err(dev->dev,
> >> +                             "failed to construct writeback connector %d (%d)\n",
> >> +                             i, ret);
> >> +                     goto fail;
> >> +             }
> >> +
> >> +             wb_conn->encoder.possible_crtcs = (1 << priv->num_crtcs) - 1;
> >> +     }
> >> +
> >>       return 0;
> >>
> >>  fail:
> >> @@ -555,6 +583,10 @@ static bool mdp5_get_scanoutpos(struct drm_device *dev, unsigned int pipe,
> >>               return false;
> >>       }
> >>
> >> +     /* unsupported for writeback: */
> >> +     if (encoder->encoder_type == DRM_MODE_ENCODER_VIRTUAL)
> >> +             return false;
> >> +
> >>       vsw = mode->crtc_vsync_end - mode->crtc_vsync_start;
> >>       vbp = mode->crtc_vtotal - mode->crtc_vsync_end;
> >>
> >> @@ -610,6 +642,10 @@ static u32 mdp5_get_vblank_counter(struct drm_device *dev, unsigned int pipe)
> >>       if (!encoder)
> >>               return 0;
> >>
> >> +     /* unsupported for writeback: */
> >> +     if (encoder->encoder_type == DRM_MODE_ENCODER_VIRTUAL)
> >> +             return 0;
> >> +
> >>       return mdp5_encoder_get_framecount(encoder);
> >>  }
> >>
> >> diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.h b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.h
> >> index 425a03d213e5..be0f93ef33e1 100644
> >> --- a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.h
> >> +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.h
> >> @@ -18,6 +18,8 @@
> >>  #ifndef __MDP5_KMS_H__
> >>  #define __MDP5_KMS_H__
> >>
> >> +#include <drm/drm_writeback.h>
> >> +
> >>  #include "msm_drv.h"
> >>  #include "msm_kms.h"
> >>  #include "disp/mdp_kms.h"
> >> @@ -251,7 +253,7 @@ static inline uint32_t intf2vblank(struct mdp5_hw_mixer *mixer,
> >>               return MDP5_IRQ_PING_PONG_0_RD_PTR << mixer->pp;
> >>
> >>       if (intf->type == INTF_WB)
> >> -             return MDP5_IRQ_WB_2_DONE;
> >> +             return MDP5_IRQ_WB_2_DONE | MDP5_IRQ_WB_0_DONE | MDP5_IRQ_WB_1_DONE;
> >>
> >>       switch (intf->num) {
> >>       case 0:  return MDP5_IRQ_INTF0_VSYNC;
> >> @@ -330,4 +332,7 @@ static inline int mdp5_cmd_encoder_set_split_display(
> >>  }
> >>  #endif
> >>
> >> +struct drm_writeback_connector *mdp5_wb_connector_init(struct drm_device *dev,
> >> +             struct mdp5_ctl *ctl, unsigned wb_id);
> >> +
> >>  #endif /* __MDP5_KMS_H__ */
> >> diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_wb.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_wb.c
> >> new file mode 100644
> >> index 000000000000..3dabd0a1aa8b
> >> --- /dev/null
> >> +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_wb.c
> >> @@ -0,0 +1,367 @@
> >> +/*
> >> + * Copyright (C) 2018 Red Hat
> >> + * Author: Rob Clark <robdclark@gmail.com>
> >> + *
> >> + * This program is free software; you can redistribute it and/or modify it
> >> + * under the terms of the GNU General Public License version 2 as published by
> >> + * the Free Software Foundation.
> >> + *
> >> + * This program is distributed in the hope that it will be useful, but WITHOUT
> >> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> >> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
> >> + * more details.
> >> + *
> >> + * You should have received a copy of the GNU General Public License along with
> >> + * this program.  If not, see <http://www.gnu.org/licenses/>.
> >
> > SPDX license
> >
> >> + */
> >> +
> >> +#include "mdp5_kms.h"
> >> +
> >> +/*
> >> + * Writeback connector/encoder implementation:
> >> + */
> >> +
> >> +struct mdp5_wb_connector {
> >> +     struct drm_writeback_connector base;
> >> +
> >> +     u32 nformats;
> >> +     u32 formats[32];
> >> +
> >> +     unsigned id;
> >> +     struct mdp5_ctl *ctl;
> >> +     struct mdp5_interface *intf;
> >> +
> >> +     struct mdp_irq wb_done;
> >> +};
> >> +#define to_mdp5_wb_connector(x) container_of(x, struct mdp5_wb_connector, base)
> >> +
> >> +
> >> +static void mdp5_wb_connector_atomic_commit(struct drm_connector *connector,
> >> +             struct drm_writeback_job *job);
> >> +
> >> +static int mdp5_wb_connector_get_modes(struct drm_connector *connector)
> >> +{
> >> +     struct drm_device *dev = connector->dev;
> >> +
> >> +     return drm_add_modes_noedid(connector, dev->mode_config.max_width,
> >> +                     dev->mode_config.max_height);
> >
> > I guess the plan is to let userspace provide user-defined modes for wb? Is it
> > ever useful to have this token mode?
> 
> /me shrugs
> 
> I just copied this from the malidp wb patch.  This actually does add
> multiple modes (all the dmt modes that fit within max width/height).
> Not sure if that is useful or not.

Ah, yeah, that does seem useful for lazy testing. Might as well leave it in.

> 
> >
> >> +}
> >> +
> >> +static enum drm_mode_status
> >> +mdp5_wb_connector_mode_valid(struct drm_connector *connector,
> >> +             struct drm_display_mode *mode)
> >> +{
> >> +     struct drm_device *dev = connector->dev;
> >> +     struct drm_mode_config *mode_config = &dev->mode_config;
> >> +     int w = mode->hdisplay, h = mode->vdisplay;
> >> +
> >> +     if ((w < mode_config->min_width) || (w > mode_config->max_width))
> >> +             return MODE_BAD_HVALUE;
> >> +
> >> +     if ((h < mode_config->min_height) || (h > mode_config->max_height))
> >> +             return MODE_BAD_VVALUE;
> >> +
> >> +     return MODE_OK;
> >> +}
> >
> > Might be useful for these to migrate into drm_writeback_connector.c as helpers.
> 
> Even the _get_modes(), tbh, since I think all of these ended up the
> same as malidp..

Works for me!

> 
> >
> >> +
> >> +const struct drm_connector_helper_funcs mdp5_wb_connector_helper_funcs = {
> >> +     .get_modes = mdp5_wb_connector_get_modes,
> >> +     .mode_valid = mdp5_wb_connector_mode_valid,
> >> +     .atomic_commit = mdp5_wb_connector_atomic_commit,
> >> +};
> >> +
> >> +static enum drm_connector_status
> >> +mdp5_wb_connector_detect(struct drm_connector *connector, bool force)
> >> +{
> >> +     return connector_status_disconnected;
> >> +}
> >
> > This should be a helper as well.
> >
> >> +
> >> +static void mdp5_wb_connector_destroy(struct drm_connector *connector)
> >> +{
> >> +     drm_connector_cleanup(connector);
> >> +}
> >
> > Just use drm_connector_cleanup directly below
> >
> 
> actually, I seem to have lost a fixup, there is some memory to free in
> _destroy()
> 
> And I suspect we need a drm_writeback_connector_cleanup()..
> 
> >> +
> >> +static const struct drm_connector_funcs mdp5_wb_connector_funcs = {
> >> +     .reset = drm_atomic_helper_connector_reset,
> >> +     .detect = mdp5_wb_connector_detect,
> >> +     .fill_modes = drm_helper_probe_single_connector_modes,
> >> +     .destroy = mdp5_wb_connector_destroy,
> >> +     .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
> >> +     .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> >> +};
> >> +
> >> +static int
> >> +mdp5_wb_encoder_atomic_check(struct drm_encoder *encoder,
> >> +             struct drm_crtc_state *crtc_state,
> >> +             struct drm_connector_state *conn_state)
> >> +{
> >> +     struct msm_drm_private *priv = encoder->dev->dev_private;
> >> +     struct mdp5_crtc_state *mdp5_cstate = to_mdp5_crtc_state(crtc_state);
> >> +     struct mdp5_wb_connector *mdp5_wb = to_mdp5_wb_connector(
> >> +             to_wb_connector(conn_state->connector));
> >> +     struct drm_framebuffer *fb;
> >> +     const struct msm_format *format;
> >> +     const struct mdp_format *mdp_fmt;
> >> +     struct drm_format_name_buf format_name;
> >> +     int ret;
> >> +
> >> +     if (!conn_state->writeback_job || !conn_state->writeback_job->fb)
> >> +             return 0;
> >> +
> >> +     fb = conn_state->writeback_job->fb;
> >> +
> >> +     DBG("wb[%u]: check writeback %ux%u@%s", mdp5_wb->id,
> >> +             fb->width, fb->height,
> >> +             drm_get_format_name(fb->format->format, &format_name));
> >> +
> >> +     format = mdp_get_format(priv->kms, fb->format->format);
> >> +     if (!format) {
> >> +             DBG("Invalid pixel format!");
> >> +             return -EINVAL;
> >> +     }
> >> +
> >> +     mdp_fmt = to_mdp_format(format);
> >> +     if (MDP_FORMAT_IS_YUV(mdp_fmt)) {
> >> +             switch (mdp_fmt->chroma_sample) {
> >> +             case CHROMA_420:
> >> +             case CHROMA_H2V1:
> >> +                     /* supported */
> >> +                     break;
> >> +             case CHROMA_H1V2:
> >> +             default:
> >> +                     DBG("unsupported wb chroma samp=%d\n",
> >> +                             mdp_fmt->chroma_sample);
> >> +                     return -EINVAL;
> >> +             }
> >> +     }
> >> +
> >> +     /* TODO I think we would prefer to have proper prepare_fb()/cleanup_fb()
> >> +      * vfuncs, as with plane..  Also, where to unprepare?
> >> +      */
> >> +     ret = msm_framebuffer_prepare(fb, priv->kms->aspace);
> >> +     if (ret)
> >> +             return ret;
> >> +
> >> +     mdp5_cstate->ctl = mdp5_wb->ctl;
> >> +     mdp5_cstate->pipeline.intf = mdp5_wb->intf;
> >> +     mdp5_cstate->defer_start = true;
> >> +
> >> +     return 0;
> >> +}
> >> +
> >> +static void
> >> +wb_csc_setup(struct mdp5_kms *mdp5_kms, u32 wb_id, struct csc_cfg *csc)
> >> +{
> >> +     uint32_t  i;
> >> +     uint32_t *matrix;
> >> +
> >> +     if (unlikely(!csc))
> >> +             return;
> >> +
> >> +     matrix = csc->matrix;
> >> +     mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_0(wb_id),
> >> +             MDP5_WB_CSC_MATRIX_COEFF_0_COEFF_11(matrix[0]) |
> >> +             MDP5_WB_CSC_MATRIX_COEFF_0_COEFF_12(matrix[1]));
> >> +     mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_1(wb_id),
> >> +             MDP5_WB_CSC_MATRIX_COEFF_1_COEFF_13(matrix[2]) |
> >> +             MDP5_WB_CSC_MATRIX_COEFF_1_COEFF_21(matrix[3]));
> >> +     mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_2(wb_id),
> >> +             MDP5_WB_CSC_MATRIX_COEFF_2_COEFF_22(matrix[4]) |
> >> +             MDP5_WB_CSC_MATRIX_COEFF_2_COEFF_23(matrix[5]));
> >> +     mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_3(wb_id),
> >> +             MDP5_WB_CSC_MATRIX_COEFF_3_COEFF_31(matrix[6]) |
> >> +             MDP5_WB_CSC_MATRIX_COEFF_3_COEFF_32(matrix[7]));
> >> +     mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_4(wb_id),
> >> +             MDP5_WB_CSC_MATRIX_COEFF_4_COEFF_33(matrix[8]));
> >> +
> >> +     for (i = 0; i < ARRAY_SIZE(csc->pre_bias); i++) {
> >> +             uint32_t *pre_clamp = csc->pre_clamp;
> >> +             uint32_t *post_clamp = csc->post_clamp;
> >> +
> >> +             mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_COMP_PRECLAMP(wb_id, i),
> >> +                     MDP5_WB_CSC_COMP_PRECLAMP_REG_HIGH(pre_clamp[2*i+1]) |
> >> +                     MDP5_WB_CSC_COMP_PRECLAMP_REG_LOW(pre_clamp[2*i]));
> >> +
> >> +             mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_COMP_POSTCLAMP(wb_id, i),
> >> +                     MDP5_WB_CSC_COMP_POSTCLAMP_REG_HIGH(post_clamp[2*i+1]) |
> >> +                     MDP5_WB_CSC_COMP_POSTCLAMP_REG_LOW(post_clamp[2*i]));
> >> +
> >> +             mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_COMP_PREBIAS(wb_id, i),
> >> +                     MDP5_WB_CSC_COMP_PREBIAS_REG_VALUE(csc->pre_bias[i]));
> >> +
> >> +             mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_COMP_POSTBIAS(wb_id, i),
> >> +                     MDP5_WB_CSC_COMP_POSTBIAS_REG_VALUE(csc->post_bias[i]));
> >> +     }
> >> +}
> >> +
> >> +static void
> >> +mdp5_wb_connector_atomic_commit(struct drm_connector *connector,
> >> +             struct drm_writeback_job *job)
> >> +{
> >> +     struct msm_drm_private *priv = connector->dev->dev_private;
> >> +     struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(priv->kms));
> >> +     struct drm_connector_state *conn_state = connector->state;
> >> +     struct drm_writeback_connector *wb_conn = to_wb_connector(connector);
> >> +     struct mdp5_crtc_state *mdp5_crtc_state =
> >> +             to_mdp5_crtc_state(wb_conn->encoder.crtc->state);
> >> +     struct mdp5_wb_connector *mdp5_wb = to_mdp5_wb_connector(wb_conn);
> >> +     struct drm_framebuffer *fb = job->fb;
> >> +     struct drm_format_name_buf format_name;
> >> +     const struct mdp_format *fmt =
> >> +             to_mdp_format(mdp_get_format(priv->kms, fb->format->format));
> >> +     u32 ystride0, ystride1, outsize;
> >> +     u32 dst_format, pattern, opmode = 0;
> >> +
> >> +     DBG("wb[%u]: kick writeback %ux%u@%s", mdp5_wb->id,
> >> +             fb->width, fb->height,
> >> +             drm_get_format_name(fb->format->format, &format_name));
> >> +
> >> +     /* queue job before anything that can trigger completion irq */
> >> +     drm_writeback_queue_job(wb_conn, job);
> >> +     conn_state->writeback_job = NULL;
> >> +
> >> +     mdp_irq_register(&mdp5_kms->base, &mdp5_wb->wb_done);
> >> +
> >> +     if (MDP_FORMAT_IS_YUV(fmt)) {
> >> +             wb_csc_setup(mdp5_kms, mdp5_wb->id,
> >> +                     mdp_get_default_csc_cfg(CSC_RGB2YUV));
> >> +
> >> +             opmode |= MDP5_WB_DST_OP_MODE_CSC_EN |
> >> +                     MDP5_WB_DST_OP_MODE_CSC_SRC_DATA_FORMAT(DATA_FORMAT_RGB) |
> >> +                     MDP5_WB_DST_OP_MODE_CSC_DST_DATA_FORMAT(DATA_FORMAT_YUV);
> >> +
> >> +             switch (fmt->chroma_sample) {
> >> +             case CHROMA_420:
> >> +             case CHROMA_H2V1:
> >> +                     opmode |= MDP5_WB_DST_OP_MODE_CHROMA_DWN_SAMPLE_EN;
> >> +                     break;
> >> +             case CHROMA_H1V2:
> >> +             default:
> >> +                     WARN(1, "unsupported wb chroma samp=%d\n",
> >> +                             fmt->chroma_sample);
> >> +                     return;
> >> +             }
> >> +     }
> >> +
> >> +     dst_format = MDP5_WB_DST_FORMAT_DST_CHROMA_SAMP(fmt->chroma_sample) |
> >> +             MDP5_WB_DST_FORMAT_WRITE_PLANES(fmt->fetch_type) |
> >> +             MDP5_WB_DST_FORMAT_DSTC3_OUT(fmt->bpc_a) |
> >> +             MDP5_WB_DST_FORMAT_DSTC2_OUT(fmt->bpc_r) |
> >> +             MDP5_WB_DST_FORMAT_DSTC1_OUT(fmt->bpc_b) |
> >> +             MDP5_WB_DST_FORMAT_DSTC0_OUT(fmt->bpc_g) |
> >> +             COND(fmt->unpack_tight, MDP5_WB_DST_FORMAT_PACK_TIGHT) |
> >> +             MDP5_WB_DST_FORMAT_PACK_COUNT(fmt->unpack_count - 1) |
> >> +             MDP5_WB_DST_FORMAT_DST_BPP(fmt->cpp - 1);
> >> +
> >> +     if (fmt->bpc_a || fmt->alpha_enable) {
> >> +             dst_format |= MDP5_WB_DST_FORMAT_DSTC3_EN;
> >> +             if (!fmt->alpha_enable)
> >> +                     dst_format |= MDP5_WB_DST_FORMAT_DST_ALPHA_X;
> >> +     }
> >> +
> >> +     pattern = MDP5_WB_DST_PACK_PATTERN_ELEMENT3(fmt->unpack[3]) |
> >> +             MDP5_WB_DST_PACK_PATTERN_ELEMENT2(fmt->unpack[2]) |
> >> +             MDP5_WB_DST_PACK_PATTERN_ELEMENT1(fmt->unpack[1]) |
> >> +             MDP5_WB_DST_PACK_PATTERN_ELEMENT0(fmt->unpack[0]);
> >> +
> >> +     ystride0 = MDP5_WB_DST_YSTRIDE0_DST0_YSTRIDE(fb->pitches[0]) |
> >> +             MDP5_WB_DST_YSTRIDE0_DST1_YSTRIDE(fb->pitches[1]);
> >> +     ystride1 = MDP5_WB_DST_YSTRIDE1_DST2_YSTRIDE(fb->pitches[2]) |
> >> +             MDP5_WB_DST_YSTRIDE1_DST3_YSTRIDE(fb->pitches[3]);
> >> +
> >> +     /* get the output resolution from WB device */
> >> +     outsize = MDP5_WB_OUT_SIZE_DST_H(fb->height) |
> >> +             MDP5_WB_OUT_SIZE_DST_W(fb->width);
> >> +
> >> +     mdp5_write(mdp5_kms, REG_MDP5_WB_ALPHA_X_VALUE(mdp5_wb->id), 0xff);
> >> +     mdp5_write(mdp5_kms, REG_MDP5_WB_DST_FORMAT(mdp5_wb->id), dst_format);
> >> +     mdp5_write(mdp5_kms, REG_MDP5_WB_DST_OP_MODE(mdp5_wb->id), opmode);
> >> +     mdp5_write(mdp5_kms, REG_MDP5_WB_DST_PACK_PATTERN(mdp5_wb->id), pattern);
> >> +     mdp5_write(mdp5_kms, REG_MDP5_WB_DST_YSTRIDE0(mdp5_wb->id), ystride0);
> >> +     mdp5_write(mdp5_kms, REG_MDP5_WB_DST_YSTRIDE1(mdp5_wb->id), ystride1);
> >> +     mdp5_write(mdp5_kms, REG_MDP5_WB_OUT_SIZE(mdp5_wb->id), outsize);
> >> +
> >> +     mdp5_crtc_set_pipeline(wb_conn->encoder.crtc);
> >> +
> >> +     mdp5_write(mdp5_kms, REG_MDP5_WB_DST0_ADDR(mdp5_wb->id),
> >> +             msm_framebuffer_iova(fb, priv->kms->aspace, 0));
> >> +     mdp5_write(mdp5_kms, REG_MDP5_WB_DST1_ADDR(mdp5_wb->id),
> >> +             msm_framebuffer_iova(fb, priv->kms->aspace, 1));
> >> +     mdp5_write(mdp5_kms, REG_MDP5_WB_DST2_ADDR(mdp5_wb->id),
> >> +             msm_framebuffer_iova(fb, priv->kms->aspace, 2));
> >> +     mdp5_write(mdp5_kms, REG_MDP5_WB_DST3_ADDR(mdp5_wb->id),
> >> +             msm_framebuffer_iova(fb, priv->kms->aspace, 3));
> >> +
> >> +     /* Notify ctl that wb buffer is ready to trigger start */
> >> +     mdp5_ctl_commit(mdp5_wb->ctl, &mdp5_crtc_state->pipeline,
> >> +             MDP5_CTL_FLUSH_WB, true);
> >> +
> >> +     mdp5_ctl_set_encoder_state(mdp5_wb->ctl,
> >> +             &mdp5_crtc_state->pipeline, true);
> >> +}
> >> +
> >> +static void mdp5_wb_done_irq(struct mdp_irq *irq, uint32_t irqstatus)
> >> +{
> >> +     struct mdp5_wb_connector *mdp5_wb =
> >> +             container_of(irq, struct mdp5_wb_connector, wb_done);
> >> +     struct mdp5_crtc_state *mdp5_crtc_state =
> >> +             to_mdp5_crtc_state(mdp5_wb->base.encoder.crtc->state);
> >> +     struct msm_drm_private *priv = mdp5_wb->base.base.dev->dev_private;
> >> +
> >> +     mdp_irq_unregister(to_mdp_kms(priv->kms), &mdp5_wb->wb_done);
> >> +
> >> +     mdp5_ctl_set_encoder_state(mdp5_wb->ctl,
> >> +             &mdp5_crtc_state->pipeline, false);
> >> +
> >> +     drm_writeback_signal_completion(&mdp5_wb->base, 0);
> >> +}
> >> +
> >> +static const struct drm_encoder_helper_funcs mdp5_wb_encoder_helper_funcs = {
> >> +     .atomic_check = mdp5_wb_encoder_atomic_check,
> >> +};
> >> +
> >> +struct drm_writeback_connector *
> >> +mdp5_wb_connector_init(struct drm_device *dev, struct mdp5_ctl *ctl,
> >> +             unsigned wb_id)
> >> +{
> >> +     struct drm_connector *connector = NULL;
> >> +     struct mdp5_wb_connector *mdp5_wb;
> >> +
> >> +     mdp5_wb = kzalloc(sizeof(*mdp5_wb), GFP_KERNEL);
> >> +     if (!mdp5_wb)
> >> +             return ERR_PTR(-ENOMEM);
> >> +
> >> +     mdp5_wb->id = wb_id;
> >> +     mdp5_wb->ctl = ctl;
> >> +
> >> +     /* construct a dummy intf for WB: */
> >> +// TODO un-inline this (and also in interface_init())
> >> +     mdp5_wb->intf = kzalloc(sizeof(*mdp5_wb->intf), GFP_KERNEL);
> >> +     mdp5_wb->intf->num = -1;
> >> +     mdp5_wb->intf->type = INTF_WB;
> >> +     mdp5_wb->intf->mode = MDP5_INTF_WB_MODE_LINE;
> >> +     mdp5_wb->intf->idx = -1;
> >> +
> >> +     mdp5_wb->wb_done.irq = mdp5_wb_done_irq;
> >> +// TODO just register for all wb irq's until I figure out the mapping..
> >> +     mdp5_wb->wb_done.irqmask = MDP5_IRQ_WB_0_DONE | MDP5_IRQ_WB_1_DONE | MDP5_IRQ_WB_2_DONE;
> >> +
> >> +     connector = &mdp5_wb->base.base;
> >> +
> >> +     drm_connector_helper_add(connector, &mdp5_wb_connector_helper_funcs);
> >> +
> >> +     mdp5_wb->nformats = mdp_get_formats(mdp5_wb->formats,
> >> +             ARRAY_SIZE(mdp5_wb->formats), false);
> >> +
> >> +     drm_writeback_connector_init(dev,
> >> +             &mdp5_wb->base,
> >> +             &mdp5_wb_connector_funcs,
> >> +             &mdp5_wb_encoder_helper_funcs,
> >> +             mdp5_wb->formats,
> >> +             mdp5_wb->nformats);
> >> +
> >> +     connector->interlace_allowed = 0;
> >> +     connector->doublescan_allowed = 0;
> >
> > These are handled by drm_writeback_connector_init (and kzalloc)
> 
> hmm, not doublescan_allowed, although perhaps it should be.

Yeah, probably. kzalloc probably covers us either way.

Sean

> 
> BR,
> -R
> 
> >
> >> +
> >> +     return &mdp5_wb->base;
> >> +}
> >> diff --git a/drivers/gpu/drm/msm/dsi/dsi_host.c b/drivers/gpu/drm/msm/dsi/dsi_host.c
> >> index 7a03a9489708..422f524f7562 100644
> >> --- a/drivers/gpu/drm/msm/dsi/dsi_host.c
> >> +++ b/drivers/gpu/drm/msm/dsi/dsi_host.c
> >> @@ -741,7 +741,7 @@ static void dsi_intr_ctrl(struct msm_dsi_host *msm_host, u32 mask, int enable)
> >>       else
> >>               intr &= ~mask;
> >>
> >> -     DBG("intr=%x enable=%d", intr, enable);
> >> +     VERB("intr=%x enable=%d", intr, enable);
> >>
> >>       dsi_write(msm_host, REG_DSI_INTR_CTRL, intr);
> >>       spin_unlock_irqrestore(&msm_host->intr_lock, flags);
> >> @@ -1465,7 +1465,7 @@ static irqreturn_t dsi_host_irq(int irq, void *ptr)
> >>       dsi_write(msm_host, REG_DSI_INTR_CTRL, isr);
> >>       spin_unlock_irqrestore(&msm_host->intr_lock, flags);
> >>
> >> -     DBG("isr=0x%x, id=%d", isr, msm_host->id);
> >> +     VERB("isr=0x%x, id=%d", isr, msm_host->id);
> >>
> >>       if (isr & DSI_IRQ_ERROR)
> >>               dsi_error(msm_host);
> >> --
> >> 2.14.3
> >>
> >
> > --
> > Sean Paul, Software Engineer, Google / Chromium OS

-- 
Sean Paul, Software Engineer, Google / Chromium OS
_______________________________________________
Freedreno mailing list
Freedreno@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/freedreno

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [RFC 4/4] drm/msm/mdp5: writeback support
@ 2018-02-26 15:41               ` Sean Paul
  0 siblings, 0 replies; 41+ messages in thread
From: Sean Paul @ 2018-02-26 15:41 UTC (permalink / raw)
  To: Rob Clark
  Cc: Sean Paul, dri-devel, freedreno, linux-arm-msm, Brian Starkey,
	Liviu Dudau, David Airlie, Archit Taneja, Daniel Vetter,
	Laurent Pinchart, Ville Syrjälä,
	Neil Armstrong, Sushmita Susheelendra, Linux Kernel Mailing List

On Fri, Feb 23, 2018 at 01:15:54PM -0500, Rob Clark wrote:
> On Fri, Feb 23, 2018 at 11:30 AM, Sean Paul <seanpaul@chromium.org> wrote:
> > On Fri, Feb 23, 2018 at 08:17:54AM -0500, Rob Clark wrote:
> >> In a way, based on the original writeback patch from Jilai Wang, but a
> >> lot has shifted around since then.
> >>
> >> Signed-off-by: Rob Clark <robdclark@gmail.com>
> >> ---
> >>  drivers/gpu/drm/msm/Makefile              |   1 +
> >>  drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c |  23 +-
> >>  drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c  |  38 +++-
> >>  drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.h  |   7 +-
> >>  drivers/gpu/drm/msm/disp/mdp5/mdp5_wb.c   | 367 ++++++++++++++++++++++++++++++
> >>  drivers/gpu/drm/msm/dsi/dsi_host.c        |   4 +-
> >>  6 files changed, 431 insertions(+), 9 deletions(-)
> >>  create mode 100644 drivers/gpu/drm/msm/disp/mdp5/mdp5_wb.c
> >>
> >> diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
> >> index cd40c050b2d7..c9f50adef2db 100644
> >> --- a/drivers/gpu/drm/msm/Makefile
> >> +++ b/drivers/gpu/drm/msm/Makefile
> >> @@ -45,6 +45,7 @@ msm-y := \
> >>       disp/mdp5/mdp5_mixer.o \
> >>       disp/mdp5/mdp5_plane.o \
> >>       disp/mdp5/mdp5_smp.o \
> >> +     disp/mdp5/mdp5_wb.o \
> >>       msm_atomic.o \
> >>       msm_debugfs.o \
> >>       msm_drv.o \
> >> diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c
> >> index 9893e43ba6c5..b00ca88b741d 100644
> >> --- a/drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c
> >> +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c
> >> @@ -484,7 +484,11 @@ static void mdp5_crtc_atomic_enable(struct drm_crtc *crtc,
> >>       }
> >>
> >>       /* Restore vblank irq handling after power is enabled */
> >> -     drm_crtc_vblank_on(crtc);
> >> +// TODO we can't ->get_scanout_pos() for wb (since virtual intf)..
> >> +// perhaps drm core should be clever enough not to drm_reset_vblank_timestamp()
> >> +// for virtual encoders / writeback?
> >> +     if (mdp5_cstate->pipeline.intf->type != INTF_WB)
> >> +             drm_crtc_vblank_on(crtc);
> >>
> >>       mdp5_crtc_mode_set_nofb(crtc);
> >>
> >> @@ -518,7 +522,11 @@ int mdp5_crtc_setup_pipeline(struct drm_crtc *crtc,
> >>               u32 caps;
> >>               int ret;
> >>
> >> -             caps = MDP_LM_CAP_DISPLAY;
> >> +             if (pipeline->intf->type == INTF_WB)
> >> +                     caps = MDP_LM_CAP_WB;
> >> +             else
> >> +                     caps = MDP_LM_CAP_DISPLAY;
> >> +
> >>               if (need_right_mixer)
> >>                       caps |= MDP_LM_CAP_PAIR;
> >>
> >> @@ -545,6 +553,7 @@ int mdp5_crtc_setup_pipeline(struct drm_crtc *crtc,
> >>       mdp5_cstate->err_irqmask = intf2err(intf->num);
> >>       mdp5_cstate->vblank_irqmask = intf2vblank(pipeline->mixer, intf);
> >>
> >> +// XXX should we be treating WB as cmd_mode??
> >>       if ((intf->type == INTF_DSI) &&
> >>           (intf->mode == MDP5_INTF_DSI_MODE_COMMAND)) {
> >>               mdp5_cstate->pp_done_irqmask = lm2ppdone(pipeline->mixer);
> >> @@ -639,8 +648,12 @@ static int mdp5_crtc_atomic_check(struct drm_crtc *crtc,
> >>       }
> >>
> >>       /* bail out early if there aren't any planes */
> >> -     if (!cnt)
> >> -             return 0;
> >> +     if (!cnt) {
> >> +             if (!state->active)
> >> +                     return 0;
> >> +             dev_err(dev->dev, "%s has no planes!\n", crtc->name);
> >> +             return -EINVAL;
> >> +     }
> >
> > This seems unrelated?
> >
> 
> hmm, yeah, kinda.  It was a case that I hit before working out all the
> bugs in my kmscube writeback mode, but I guess isn't directly related
> to wb.
> 
> >>
> >>       hw_cfg = mdp5_cfg_get_hw_config(mdp5_kms->cfg);
> >>
> >> @@ -1160,7 +1173,7 @@ void mdp5_crtc_wait_for_commit_done(struct drm_crtc *crtc)
> >>
> >>       if (mdp5_cstate->cmd_mode)
> >>               mdp5_crtc_wait_for_pp_done(crtc);
> >> -     else
> >> +     else if (mdp5_cstate->pipeline.intf->type != INTF_WB)
> >>               mdp5_crtc_wait_for_flush_done(crtc);
> >>  }
> >>
> >> diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c
> >> index 1f44d8f15ce1..239010905637 100644
> >> --- a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c
> >> +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c
> >> @@ -427,7 +427,8 @@ static int modeset_init(struct mdp5_kms *mdp5_kms)
> >>        * the MDP5 interfaces) than the number of layer mixers present in HW,
> >>        * but let's be safe here anyway
> >>        */
> >> -     num_crtcs = min(priv->num_encoders, mdp5_kms->num_hwmixers);
> >> +     num_crtcs = min(priv->num_encoders + hw_cfg->wb.count,
> >> +                     mdp5_kms->num_hwmixers);
> >>
> >>       /*
> >>        * Construct planes equaling the number of hw pipes, and CRTCs for the
> >> @@ -482,6 +483,33 @@ static int modeset_init(struct mdp5_kms *mdp5_kms)
> >>               encoder->possible_crtcs = (1 << priv->num_crtcs) - 1;
> >>       }
> >>
> >> +     /*
> >> +      * Lastly, construct writeback connectors.
> >> +      */
> >> +     for (i = 0; i < hw_cfg->wb.count; i++) {
> >> +             struct drm_writeback_connector *wb_conn;
> >> +             struct mdp5_ctl *ctl;
> >> +
> >> +             ctl = mdp5_ctlm_request(mdp5_kms->ctlm, -1);
> >> +             if (!ctl) {
> >> +                     dev_err(dev->dev,
> >> +                             "failed to allocate ctl for writeback %d\n", i);
> >> +                     continue;
> >> +             }
> >> +
> >> +             wb_conn = mdp5_wb_connector_init(dev, ctl,
> >> +                             hw_cfg->wb.instances[i].id);
> >> +             if (IS_ERR(wb_conn)) {
> >> +                     ret = PTR_ERR(wb_conn);
> >> +                     dev_err(dev->dev,
> >> +                             "failed to construct writeback connector %d (%d)\n",
> >> +                             i, ret);
> >> +                     goto fail;
> >> +             }
> >> +
> >> +             wb_conn->encoder.possible_crtcs = (1 << priv->num_crtcs) - 1;
> >> +     }
> >> +
> >>       return 0;
> >>
> >>  fail:
> >> @@ -555,6 +583,10 @@ static bool mdp5_get_scanoutpos(struct drm_device *dev, unsigned int pipe,
> >>               return false;
> >>       }
> >>
> >> +     /* unsupported for writeback: */
> >> +     if (encoder->encoder_type == DRM_MODE_ENCODER_VIRTUAL)
> >> +             return false;
> >> +
> >>       vsw = mode->crtc_vsync_end - mode->crtc_vsync_start;
> >>       vbp = mode->crtc_vtotal - mode->crtc_vsync_end;
> >>
> >> @@ -610,6 +642,10 @@ static u32 mdp5_get_vblank_counter(struct drm_device *dev, unsigned int pipe)
> >>       if (!encoder)
> >>               return 0;
> >>
> >> +     /* unsupported for writeback: */
> >> +     if (encoder->encoder_type == DRM_MODE_ENCODER_VIRTUAL)
> >> +             return 0;
> >> +
> >>       return mdp5_encoder_get_framecount(encoder);
> >>  }
> >>
> >> diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.h b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.h
> >> index 425a03d213e5..be0f93ef33e1 100644
> >> --- a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.h
> >> +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.h
> >> @@ -18,6 +18,8 @@
> >>  #ifndef __MDP5_KMS_H__
> >>  #define __MDP5_KMS_H__
> >>
> >> +#include <drm/drm_writeback.h>
> >> +
> >>  #include "msm_drv.h"
> >>  #include "msm_kms.h"
> >>  #include "disp/mdp_kms.h"
> >> @@ -251,7 +253,7 @@ static inline uint32_t intf2vblank(struct mdp5_hw_mixer *mixer,
> >>               return MDP5_IRQ_PING_PONG_0_RD_PTR << mixer->pp;
> >>
> >>       if (intf->type == INTF_WB)
> >> -             return MDP5_IRQ_WB_2_DONE;
> >> +             return MDP5_IRQ_WB_2_DONE | MDP5_IRQ_WB_0_DONE | MDP5_IRQ_WB_1_DONE;
> >>
> >>       switch (intf->num) {
> >>       case 0:  return MDP5_IRQ_INTF0_VSYNC;
> >> @@ -330,4 +332,7 @@ static inline int mdp5_cmd_encoder_set_split_display(
> >>  }
> >>  #endif
> >>
> >> +struct drm_writeback_connector *mdp5_wb_connector_init(struct drm_device *dev,
> >> +             struct mdp5_ctl *ctl, unsigned wb_id);
> >> +
> >>  #endif /* __MDP5_KMS_H__ */
> >> diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_wb.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_wb.c
> >> new file mode 100644
> >> index 000000000000..3dabd0a1aa8b
> >> --- /dev/null
> >> +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_wb.c
> >> @@ -0,0 +1,367 @@
> >> +/*
> >> + * Copyright (C) 2018 Red Hat
> >> + * Author: Rob Clark <robdclark@gmail.com>
> >> + *
> >> + * This program is free software; you can redistribute it and/or modify it
> >> + * under the terms of the GNU General Public License version 2 as published by
> >> + * the Free Software Foundation.
> >> + *
> >> + * This program is distributed in the hope that it will be useful, but WITHOUT
> >> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> >> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
> >> + * more details.
> >> + *
> >> + * You should have received a copy of the GNU General Public License along with
> >> + * this program.  If not, see <http://www.gnu.org/licenses/>.
> >
> > SPDX license
> >
> >> + */
> >> +
> >> +#include "mdp5_kms.h"
> >> +
> >> +/*
> >> + * Writeback connector/encoder implementation:
> >> + */
> >> +
> >> +struct mdp5_wb_connector {
> >> +     struct drm_writeback_connector base;
> >> +
> >> +     u32 nformats;
> >> +     u32 formats[32];
> >> +
> >> +     unsigned id;
> >> +     struct mdp5_ctl *ctl;
> >> +     struct mdp5_interface *intf;
> >> +
> >> +     struct mdp_irq wb_done;
> >> +};
> >> +#define to_mdp5_wb_connector(x) container_of(x, struct mdp5_wb_connector, base)
> >> +
> >> +
> >> +static void mdp5_wb_connector_atomic_commit(struct drm_connector *connector,
> >> +             struct drm_writeback_job *job);
> >> +
> >> +static int mdp5_wb_connector_get_modes(struct drm_connector *connector)
> >> +{
> >> +     struct drm_device *dev = connector->dev;
> >> +
> >> +     return drm_add_modes_noedid(connector, dev->mode_config.max_width,
> >> +                     dev->mode_config.max_height);
> >
> > I guess the plan is to let userspace provide user-defined modes for wb? Is it
> > ever useful to have this token mode?
> 
> /me shrugs
> 
> I just copied this from the malidp wb patch.  This actually does add
> multiple modes (all the dmt modes that fit within max width/height).
> Not sure if that is useful or not.

Ah, yeah, that does seem useful for lazy testing. Might as well leave it in.

> 
> >
> >> +}
> >> +
> >> +static enum drm_mode_status
> >> +mdp5_wb_connector_mode_valid(struct drm_connector *connector,
> >> +             struct drm_display_mode *mode)
> >> +{
> >> +     struct drm_device *dev = connector->dev;
> >> +     struct drm_mode_config *mode_config = &dev->mode_config;
> >> +     int w = mode->hdisplay, h = mode->vdisplay;
> >> +
> >> +     if ((w < mode_config->min_width) || (w > mode_config->max_width))
> >> +             return MODE_BAD_HVALUE;
> >> +
> >> +     if ((h < mode_config->min_height) || (h > mode_config->max_height))
> >> +             return MODE_BAD_VVALUE;
> >> +
> >> +     return MODE_OK;
> >> +}
> >
> > Might be useful for these to migrate into drm_writeback_connector.c as helpers.
> 
> Even the _get_modes(), tbh, since I think all of these ended up the
> same as malidp..

Works for me!

> 
> >
> >> +
> >> +const struct drm_connector_helper_funcs mdp5_wb_connector_helper_funcs = {
> >> +     .get_modes = mdp5_wb_connector_get_modes,
> >> +     .mode_valid = mdp5_wb_connector_mode_valid,
> >> +     .atomic_commit = mdp5_wb_connector_atomic_commit,
> >> +};
> >> +
> >> +static enum drm_connector_status
> >> +mdp5_wb_connector_detect(struct drm_connector *connector, bool force)
> >> +{
> >> +     return connector_status_disconnected;
> >> +}
> >
> > This should be a helper as well.
> >
> >> +
> >> +static void mdp5_wb_connector_destroy(struct drm_connector *connector)
> >> +{
> >> +     drm_connector_cleanup(connector);
> >> +}
> >
> > Just use drm_connector_cleanup directly below
> >
> 
> actually, I seem to have lost a fixup, there is some memory to free in
> _destroy()
> 
> And I suspect we need a drm_writeback_connector_cleanup()..
> 
> >> +
> >> +static const struct drm_connector_funcs mdp5_wb_connector_funcs = {
> >> +     .reset = drm_atomic_helper_connector_reset,
> >> +     .detect = mdp5_wb_connector_detect,
> >> +     .fill_modes = drm_helper_probe_single_connector_modes,
> >> +     .destroy = mdp5_wb_connector_destroy,
> >> +     .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
> >> +     .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> >> +};
> >> +
> >> +static int
> >> +mdp5_wb_encoder_atomic_check(struct drm_encoder *encoder,
> >> +             struct drm_crtc_state *crtc_state,
> >> +             struct drm_connector_state *conn_state)
> >> +{
> >> +     struct msm_drm_private *priv = encoder->dev->dev_private;
> >> +     struct mdp5_crtc_state *mdp5_cstate = to_mdp5_crtc_state(crtc_state);
> >> +     struct mdp5_wb_connector *mdp5_wb = to_mdp5_wb_connector(
> >> +             to_wb_connector(conn_state->connector));
> >> +     struct drm_framebuffer *fb;
> >> +     const struct msm_format *format;
> >> +     const struct mdp_format *mdp_fmt;
> >> +     struct drm_format_name_buf format_name;
> >> +     int ret;
> >> +
> >> +     if (!conn_state->writeback_job || !conn_state->writeback_job->fb)
> >> +             return 0;
> >> +
> >> +     fb = conn_state->writeback_job->fb;
> >> +
> >> +     DBG("wb[%u]: check writeback %ux%u@%s", mdp5_wb->id,
> >> +             fb->width, fb->height,
> >> +             drm_get_format_name(fb->format->format, &format_name));
> >> +
> >> +     format = mdp_get_format(priv->kms, fb->format->format);
> >> +     if (!format) {
> >> +             DBG("Invalid pixel format!");
> >> +             return -EINVAL;
> >> +     }
> >> +
> >> +     mdp_fmt = to_mdp_format(format);
> >> +     if (MDP_FORMAT_IS_YUV(mdp_fmt)) {
> >> +             switch (mdp_fmt->chroma_sample) {
> >> +             case CHROMA_420:
> >> +             case CHROMA_H2V1:
> >> +                     /* supported */
> >> +                     break;
> >> +             case CHROMA_H1V2:
> >> +             default:
> >> +                     DBG("unsupported wb chroma samp=%d\n",
> >> +                             mdp_fmt->chroma_sample);
> >> +                     return -EINVAL;
> >> +             }
> >> +     }
> >> +
> >> +     /* TODO I think we would prefer to have proper prepare_fb()/cleanup_fb()
> >> +      * vfuncs, as with plane..  Also, where to unprepare?
> >> +      */
> >> +     ret = msm_framebuffer_prepare(fb, priv->kms->aspace);
> >> +     if (ret)
> >> +             return ret;
> >> +
> >> +     mdp5_cstate->ctl = mdp5_wb->ctl;
> >> +     mdp5_cstate->pipeline.intf = mdp5_wb->intf;
> >> +     mdp5_cstate->defer_start = true;
> >> +
> >> +     return 0;
> >> +}
> >> +
> >> +static void
> >> +wb_csc_setup(struct mdp5_kms *mdp5_kms, u32 wb_id, struct csc_cfg *csc)
> >> +{
> >> +     uint32_t  i;
> >> +     uint32_t *matrix;
> >> +
> >> +     if (unlikely(!csc))
> >> +             return;
> >> +
> >> +     matrix = csc->matrix;
> >> +     mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_0(wb_id),
> >> +             MDP5_WB_CSC_MATRIX_COEFF_0_COEFF_11(matrix[0]) |
> >> +             MDP5_WB_CSC_MATRIX_COEFF_0_COEFF_12(matrix[1]));
> >> +     mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_1(wb_id),
> >> +             MDP5_WB_CSC_MATRIX_COEFF_1_COEFF_13(matrix[2]) |
> >> +             MDP5_WB_CSC_MATRIX_COEFF_1_COEFF_21(matrix[3]));
> >> +     mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_2(wb_id),
> >> +             MDP5_WB_CSC_MATRIX_COEFF_2_COEFF_22(matrix[4]) |
> >> +             MDP5_WB_CSC_MATRIX_COEFF_2_COEFF_23(matrix[5]));
> >> +     mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_3(wb_id),
> >> +             MDP5_WB_CSC_MATRIX_COEFF_3_COEFF_31(matrix[6]) |
> >> +             MDP5_WB_CSC_MATRIX_COEFF_3_COEFF_32(matrix[7]));
> >> +     mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_4(wb_id),
> >> +             MDP5_WB_CSC_MATRIX_COEFF_4_COEFF_33(matrix[8]));
> >> +
> >> +     for (i = 0; i < ARRAY_SIZE(csc->pre_bias); i++) {
> >> +             uint32_t *pre_clamp = csc->pre_clamp;
> >> +             uint32_t *post_clamp = csc->post_clamp;
> >> +
> >> +             mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_COMP_PRECLAMP(wb_id, i),
> >> +                     MDP5_WB_CSC_COMP_PRECLAMP_REG_HIGH(pre_clamp[2*i+1]) |
> >> +                     MDP5_WB_CSC_COMP_PRECLAMP_REG_LOW(pre_clamp[2*i]));
> >> +
> >> +             mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_COMP_POSTCLAMP(wb_id, i),
> >> +                     MDP5_WB_CSC_COMP_POSTCLAMP_REG_HIGH(post_clamp[2*i+1]) |
> >> +                     MDP5_WB_CSC_COMP_POSTCLAMP_REG_LOW(post_clamp[2*i]));
> >> +
> >> +             mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_COMP_PREBIAS(wb_id, i),
> >> +                     MDP5_WB_CSC_COMP_PREBIAS_REG_VALUE(csc->pre_bias[i]));
> >> +
> >> +             mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_COMP_POSTBIAS(wb_id, i),
> >> +                     MDP5_WB_CSC_COMP_POSTBIAS_REG_VALUE(csc->post_bias[i]));
> >> +     }
> >> +}
> >> +
> >> +static void
> >> +mdp5_wb_connector_atomic_commit(struct drm_connector *connector,
> >> +             struct drm_writeback_job *job)
> >> +{
> >> +     struct msm_drm_private *priv = connector->dev->dev_private;
> >> +     struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(priv->kms));
> >> +     struct drm_connector_state *conn_state = connector->state;
> >> +     struct drm_writeback_connector *wb_conn = to_wb_connector(connector);
> >> +     struct mdp5_crtc_state *mdp5_crtc_state =
> >> +             to_mdp5_crtc_state(wb_conn->encoder.crtc->state);
> >> +     struct mdp5_wb_connector *mdp5_wb = to_mdp5_wb_connector(wb_conn);
> >> +     struct drm_framebuffer *fb = job->fb;
> >> +     struct drm_format_name_buf format_name;
> >> +     const struct mdp_format *fmt =
> >> +             to_mdp_format(mdp_get_format(priv->kms, fb->format->format));
> >> +     u32 ystride0, ystride1, outsize;
> >> +     u32 dst_format, pattern, opmode = 0;
> >> +
> >> +     DBG("wb[%u]: kick writeback %ux%u@%s", mdp5_wb->id,
> >> +             fb->width, fb->height,
> >> +             drm_get_format_name(fb->format->format, &format_name));
> >> +
> >> +     /* queue job before anything that can trigger completion irq */
> >> +     drm_writeback_queue_job(wb_conn, job);
> >> +     conn_state->writeback_job = NULL;
> >> +
> >> +     mdp_irq_register(&mdp5_kms->base, &mdp5_wb->wb_done);
> >> +
> >> +     if (MDP_FORMAT_IS_YUV(fmt)) {
> >> +             wb_csc_setup(mdp5_kms, mdp5_wb->id,
> >> +                     mdp_get_default_csc_cfg(CSC_RGB2YUV));
> >> +
> >> +             opmode |= MDP5_WB_DST_OP_MODE_CSC_EN |
> >> +                     MDP5_WB_DST_OP_MODE_CSC_SRC_DATA_FORMAT(DATA_FORMAT_RGB) |
> >> +                     MDP5_WB_DST_OP_MODE_CSC_DST_DATA_FORMAT(DATA_FORMAT_YUV);
> >> +
> >> +             switch (fmt->chroma_sample) {
> >> +             case CHROMA_420:
> >> +             case CHROMA_H2V1:
> >> +                     opmode |= MDP5_WB_DST_OP_MODE_CHROMA_DWN_SAMPLE_EN;
> >> +                     break;
> >> +             case CHROMA_H1V2:
> >> +             default:
> >> +                     WARN(1, "unsupported wb chroma samp=%d\n",
> >> +                             fmt->chroma_sample);
> >> +                     return;
> >> +             }
> >> +     }
> >> +
> >> +     dst_format = MDP5_WB_DST_FORMAT_DST_CHROMA_SAMP(fmt->chroma_sample) |
> >> +             MDP5_WB_DST_FORMAT_WRITE_PLANES(fmt->fetch_type) |
> >> +             MDP5_WB_DST_FORMAT_DSTC3_OUT(fmt->bpc_a) |
> >> +             MDP5_WB_DST_FORMAT_DSTC2_OUT(fmt->bpc_r) |
> >> +             MDP5_WB_DST_FORMAT_DSTC1_OUT(fmt->bpc_b) |
> >> +             MDP5_WB_DST_FORMAT_DSTC0_OUT(fmt->bpc_g) |
> >> +             COND(fmt->unpack_tight, MDP5_WB_DST_FORMAT_PACK_TIGHT) |
> >> +             MDP5_WB_DST_FORMAT_PACK_COUNT(fmt->unpack_count - 1) |
> >> +             MDP5_WB_DST_FORMAT_DST_BPP(fmt->cpp - 1);
> >> +
> >> +     if (fmt->bpc_a || fmt->alpha_enable) {
> >> +             dst_format |= MDP5_WB_DST_FORMAT_DSTC3_EN;
> >> +             if (!fmt->alpha_enable)
> >> +                     dst_format |= MDP5_WB_DST_FORMAT_DST_ALPHA_X;
> >> +     }
> >> +
> >> +     pattern = MDP5_WB_DST_PACK_PATTERN_ELEMENT3(fmt->unpack[3]) |
> >> +             MDP5_WB_DST_PACK_PATTERN_ELEMENT2(fmt->unpack[2]) |
> >> +             MDP5_WB_DST_PACK_PATTERN_ELEMENT1(fmt->unpack[1]) |
> >> +             MDP5_WB_DST_PACK_PATTERN_ELEMENT0(fmt->unpack[0]);
> >> +
> >> +     ystride0 = MDP5_WB_DST_YSTRIDE0_DST0_YSTRIDE(fb->pitches[0]) |
> >> +             MDP5_WB_DST_YSTRIDE0_DST1_YSTRIDE(fb->pitches[1]);
> >> +     ystride1 = MDP5_WB_DST_YSTRIDE1_DST2_YSTRIDE(fb->pitches[2]) |
> >> +             MDP5_WB_DST_YSTRIDE1_DST3_YSTRIDE(fb->pitches[3]);
> >> +
> >> +     /* get the output resolution from WB device */
> >> +     outsize = MDP5_WB_OUT_SIZE_DST_H(fb->height) |
> >> +             MDP5_WB_OUT_SIZE_DST_W(fb->width);
> >> +
> >> +     mdp5_write(mdp5_kms, REG_MDP5_WB_ALPHA_X_VALUE(mdp5_wb->id), 0xff);
> >> +     mdp5_write(mdp5_kms, REG_MDP5_WB_DST_FORMAT(mdp5_wb->id), dst_format);
> >> +     mdp5_write(mdp5_kms, REG_MDP5_WB_DST_OP_MODE(mdp5_wb->id), opmode);
> >> +     mdp5_write(mdp5_kms, REG_MDP5_WB_DST_PACK_PATTERN(mdp5_wb->id), pattern);
> >> +     mdp5_write(mdp5_kms, REG_MDP5_WB_DST_YSTRIDE0(mdp5_wb->id), ystride0);
> >> +     mdp5_write(mdp5_kms, REG_MDP5_WB_DST_YSTRIDE1(mdp5_wb->id), ystride1);
> >> +     mdp5_write(mdp5_kms, REG_MDP5_WB_OUT_SIZE(mdp5_wb->id), outsize);
> >> +
> >> +     mdp5_crtc_set_pipeline(wb_conn->encoder.crtc);
> >> +
> >> +     mdp5_write(mdp5_kms, REG_MDP5_WB_DST0_ADDR(mdp5_wb->id),
> >> +             msm_framebuffer_iova(fb, priv->kms->aspace, 0));
> >> +     mdp5_write(mdp5_kms, REG_MDP5_WB_DST1_ADDR(mdp5_wb->id),
> >> +             msm_framebuffer_iova(fb, priv->kms->aspace, 1));
> >> +     mdp5_write(mdp5_kms, REG_MDP5_WB_DST2_ADDR(mdp5_wb->id),
> >> +             msm_framebuffer_iova(fb, priv->kms->aspace, 2));
> >> +     mdp5_write(mdp5_kms, REG_MDP5_WB_DST3_ADDR(mdp5_wb->id),
> >> +             msm_framebuffer_iova(fb, priv->kms->aspace, 3));
> >> +
> >> +     /* Notify ctl that wb buffer is ready to trigger start */
> >> +     mdp5_ctl_commit(mdp5_wb->ctl, &mdp5_crtc_state->pipeline,
> >> +             MDP5_CTL_FLUSH_WB, true);
> >> +
> >> +     mdp5_ctl_set_encoder_state(mdp5_wb->ctl,
> >> +             &mdp5_crtc_state->pipeline, true);
> >> +}
> >> +
> >> +static void mdp5_wb_done_irq(struct mdp_irq *irq, uint32_t irqstatus)
> >> +{
> >> +     struct mdp5_wb_connector *mdp5_wb =
> >> +             container_of(irq, struct mdp5_wb_connector, wb_done);
> >> +     struct mdp5_crtc_state *mdp5_crtc_state =
> >> +             to_mdp5_crtc_state(mdp5_wb->base.encoder.crtc->state);
> >> +     struct msm_drm_private *priv = mdp5_wb->base.base.dev->dev_private;
> >> +
> >> +     mdp_irq_unregister(to_mdp_kms(priv->kms), &mdp5_wb->wb_done);
> >> +
> >> +     mdp5_ctl_set_encoder_state(mdp5_wb->ctl,
> >> +             &mdp5_crtc_state->pipeline, false);
> >> +
> >> +     drm_writeback_signal_completion(&mdp5_wb->base, 0);
> >> +}
> >> +
> >> +static const struct drm_encoder_helper_funcs mdp5_wb_encoder_helper_funcs = {
> >> +     .atomic_check = mdp5_wb_encoder_atomic_check,
> >> +};
> >> +
> >> +struct drm_writeback_connector *
> >> +mdp5_wb_connector_init(struct drm_device *dev, struct mdp5_ctl *ctl,
> >> +             unsigned wb_id)
> >> +{
> >> +     struct drm_connector *connector = NULL;
> >> +     struct mdp5_wb_connector *mdp5_wb;
> >> +
> >> +     mdp5_wb = kzalloc(sizeof(*mdp5_wb), GFP_KERNEL);
> >> +     if (!mdp5_wb)
> >> +             return ERR_PTR(-ENOMEM);
> >> +
> >> +     mdp5_wb->id = wb_id;
> >> +     mdp5_wb->ctl = ctl;
> >> +
> >> +     /* construct a dummy intf for WB: */
> >> +// TODO un-inline this (and also in interface_init())
> >> +     mdp5_wb->intf = kzalloc(sizeof(*mdp5_wb->intf), GFP_KERNEL);
> >> +     mdp5_wb->intf->num = -1;
> >> +     mdp5_wb->intf->type = INTF_WB;
> >> +     mdp5_wb->intf->mode = MDP5_INTF_WB_MODE_LINE;
> >> +     mdp5_wb->intf->idx = -1;
> >> +
> >> +     mdp5_wb->wb_done.irq = mdp5_wb_done_irq;
> >> +// TODO just register for all wb irq's until I figure out the mapping..
> >> +     mdp5_wb->wb_done.irqmask = MDP5_IRQ_WB_0_DONE | MDP5_IRQ_WB_1_DONE | MDP5_IRQ_WB_2_DONE;
> >> +
> >> +     connector = &mdp5_wb->base.base;
> >> +
> >> +     drm_connector_helper_add(connector, &mdp5_wb_connector_helper_funcs);
> >> +
> >> +     mdp5_wb->nformats = mdp_get_formats(mdp5_wb->formats,
> >> +             ARRAY_SIZE(mdp5_wb->formats), false);
> >> +
> >> +     drm_writeback_connector_init(dev,
> >> +             &mdp5_wb->base,
> >> +             &mdp5_wb_connector_funcs,
> >> +             &mdp5_wb_encoder_helper_funcs,
> >> +             mdp5_wb->formats,
> >> +             mdp5_wb->nformats);
> >> +
> >> +     connector->interlace_allowed = 0;
> >> +     connector->doublescan_allowed = 0;
> >
> > These are handled by drm_writeback_connector_init (and kzalloc)
> 
> hmm, not doublescan_allowed, although perhaps it should be.

Yeah, probably. kzalloc probably covers us either way.

Sean

> 
> BR,
> -R
> 
> >
> >> +
> >> +     return &mdp5_wb->base;
> >> +}
> >> diff --git a/drivers/gpu/drm/msm/dsi/dsi_host.c b/drivers/gpu/drm/msm/dsi/dsi_host.c
> >> index 7a03a9489708..422f524f7562 100644
> >> --- a/drivers/gpu/drm/msm/dsi/dsi_host.c
> >> +++ b/drivers/gpu/drm/msm/dsi/dsi_host.c
> >> @@ -741,7 +741,7 @@ static void dsi_intr_ctrl(struct msm_dsi_host *msm_host, u32 mask, int enable)
> >>       else
> >>               intr &= ~mask;
> >>
> >> -     DBG("intr=%x enable=%d", intr, enable);
> >> +     VERB("intr=%x enable=%d", intr, enable);
> >>
> >>       dsi_write(msm_host, REG_DSI_INTR_CTRL, intr);
> >>       spin_unlock_irqrestore(&msm_host->intr_lock, flags);
> >> @@ -1465,7 +1465,7 @@ static irqreturn_t dsi_host_irq(int irq, void *ptr)
> >>       dsi_write(msm_host, REG_DSI_INTR_CTRL, isr);
> >>       spin_unlock_irqrestore(&msm_host->intr_lock, flags);
> >>
> >> -     DBG("isr=0x%x, id=%d", isr, msm_host->id);
> >> +     VERB("isr=0x%x, id=%d", isr, msm_host->id);
> >>
> >>       if (isr & DSI_IRQ_ERROR)
> >>               dsi_error(msm_host);
> >> --
> >> 2.14.3
> >>
> >
> > --
> > Sean Paul, Software Engineer, Google / Chromium OS

-- 
Sean Paul, Software Engineer, Google / Chromium OS

^ permalink raw reply	[flat|nested] 41+ messages in thread

end of thread, other threads:[~2018-02-26 15:41 UTC | newest]

Thread overview: 41+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-02-23 13:17 [RFC 0/4] drm/msm/mdp5: writeback connector support Rob Clark
     [not found] ` <20180223131758.18362-1-robdclark-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
2018-02-23 13:17   ` [RFC 1/4] drm: Add writeback connector type Rob Clark
2018-02-23 13:17     ` Rob Clark
     [not found]     ` <20180223131758.18362-2-robdclark-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
2018-02-23 14:00       ` Liviu Dudau
2018-02-23 14:00         ` Liviu Dudau
     [not found]         ` <20180223140018.GV9111-A/Nd4k6kWRHZROr8t4l/smS4ubULX0JqMm0uRHvK7Nw@public.gmane.org>
2018-02-23 14:24           ` Rob Clark
2018-02-23 14:24             ` Rob Clark
     [not found]             ` <CAF6AEGvEZ6Z3dSHk9keb9MeQO28epeMey1zwsY51=UUHUH7FZw-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2018-02-23 14:27               ` Liviu Dudau
2018-02-23 14:27                 ` Liviu Dudau
2018-02-23 15:59       ` Sean Paul
2018-02-23 15:59         ` Sean Paul
2018-02-23 16:21         ` Liviu Dudau
2018-02-23 16:21           ` Liviu Dudau
     [not found]           ` <20180223162104.GX9111-A/Nd4k6kWRHZROr8t4l/smS4ubULX0JqMm0uRHvK7Nw@public.gmane.org>
2018-02-23 16:39             ` Sean Paul
2018-02-23 16:39               ` Sean Paul
2018-02-23 16:52               ` Liviu Dudau
2018-02-23 16:52                 ` Liviu Dudau
2018-02-23 16:25         ` Rob Clark
2018-02-23 16:25           ` Rob Clark
2018-02-23 16:43           ` Sean Paul
2018-02-23 16:43             ` Sean Paul
2018-02-23 16:48             ` Liviu Dudau
2018-02-23 16:48               ` Liviu Dudau
     [not found]               ` <20180223164858.GY9111-A/Nd4k6kWRHZROr8t4l/smS4ubULX0JqMm0uRHvK7Nw@public.gmane.org>
2018-02-23 17:04                 ` Sean Paul
2018-02-23 17:04                   ` Sean Paul
2018-02-23 13:17   ` [RFC 2/4] drm: writeback: Add out-fences for writeback connectors Rob Clark
2018-02-23 13:17     ` Rob Clark
     [not found]     ` <20180223131758.18362-3-robdclark-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
2018-02-23 16:14       ` Sean Paul
2018-02-23 16:14         ` Sean Paul
2018-02-23 13:17   ` [RFC 3/4] drm/msm/mdp5: add config for writeback pipes Rob Clark
2018-02-23 13:17     ` Rob Clark
2018-02-23 16:32     ` Sean Paul
2018-02-23 16:32       ` Sean Paul
2018-02-23 13:17   ` [RFC 4/4] drm/msm/mdp5: writeback support Rob Clark
2018-02-23 13:17     ` Rob Clark
     [not found]     ` <20180223131758.18362-5-robdclark-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
2018-02-23 16:30       ` Sean Paul
2018-02-23 16:30         ` Sean Paul
2018-02-23 18:15         ` Rob Clark
2018-02-23 18:15           ` Rob Clark
     [not found]           ` <CAF6AEGvAQu6zVO8d75YJgDLtpHJFxU0JGw8S7SR7HGOfMNPHXg-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2018-02-26 15:41             ` Sean Paul
2018-02-26 15:41               ` Sean Paul

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.