diff mbox

[16/51] drm: Add drm_flip helper

Message ID 1351188354-24233-17-git-send-email-ville.syrjala@linux.intel.com (mailing list archive)
State New, archived
Headers show

Commit Message

Ville Syrjälä Oct. 25, 2012, 6:05 p.m. UTC
From: Ville Syrjälä <ville.syrjala@linux.intel.com>

The drm_flip mechanism can be used to implement robust page flipping
support, and also to synchronize the flips on multiple hardware
scanout engines (eg. CRTCs and overlays).

Signed-off-by: Ville Syrjälä <ville.syrjala@linux.intel.com>
---
 drivers/gpu/drm/Makefile   |    2 +-
 drivers/gpu/drm/drm_flip.c |  376 ++++++++++++++++++++++++++++++++++++++++++++
 include/drm/drm_flip.h     |  244 ++++++++++++++++++++++++++++
 3 files changed, 621 insertions(+), 1 deletions(-)
 create mode 100644 drivers/gpu/drm/drm_flip.c
 create mode 100644 include/drm/drm_flip.h
diff mbox

Patch

diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 2ff5cef..f98afd8 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -12,7 +12,7 @@  drm-y       :=	drm_auth.o drm_buffer.o drm_bufs.o drm_cache.o \
 		drm_platform.o drm_sysfs.o drm_hashtab.o drm_mm.o \
 		drm_crtc.o drm_modes.o drm_edid.o \
 		drm_info.o drm_debugfs.o drm_encoder_slave.o \
-		drm_trace_points.o drm_global.o drm_prime.o
+		drm_trace_points.o drm_global.o drm_prime.o drm_flip.o
 
 drm-$(CONFIG_COMPAT) += drm_ioc32.o
 drm-$(CONFIG_DRM_GEM_CMA_HELPER) += drm_gem_cma_helper.o
diff --git a/drivers/gpu/drm/drm_flip.c b/drivers/gpu/drm/drm_flip.c
new file mode 100644
index 0000000..6ccc3f8
--- /dev/null
+++ b/drivers/gpu/drm/drm_flip.c
@@ -0,0 +1,376 @@ 
+/*
+ * Copyright (C) 2012 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Authors:
+ * Ville Syrjälä <ville.syrjala@linux.intel.com>
+ */
+
+#include <drm/drm_flip.h>
+
+static void drm_flip_driver_cleanup(struct work_struct *work)
+{
+	struct drm_flip *flip, *next;
+	struct drm_flip_driver *driver =
+		container_of(work, struct drm_flip_driver, cleanup_work);
+	LIST_HEAD(list);
+
+	spin_lock_irq(&driver->lock);
+
+	list_cut_position(&list,
+			  &driver->cleanup_list,
+			  driver->cleanup_list.prev);
+
+	spin_unlock_irq(&driver->lock);
+
+	if (list_empty(&list))
+		return;
+
+	list_for_each_entry_safe(flip, next, &list, list) {
+		struct drm_flip_helper *helper = flip->helper;
+
+		WARN_ON(!flip->finished);
+
+		helper->funcs->cleanup(flip);
+	}
+}
+
+static void drm_flip_driver_finish(struct work_struct *work)
+{
+	struct drm_flip *flip, *next;
+	struct drm_flip_driver *driver =
+		container_of(work, struct drm_flip_driver, finish_work);
+	LIST_HEAD(list);
+	bool need_cleanup = false;
+
+	spin_lock_irq(&driver->lock);
+
+	list_cut_position(&list,
+			  &driver->finish_list,
+			  driver->finish_list.prev);
+
+	spin_unlock_irq(&driver->lock);
+
+	if (list_empty(&list))
+		return;
+
+	list_for_each_entry_safe(flip, next, &list, list) {
+		struct drm_flip_helper *helper = flip->helper;
+
+		helper->funcs->finish(flip);
+
+		spin_lock_irq(&driver->lock);
+
+		flip->finished = true;
+
+		/*
+		 * It's possible that drm_flip_set_scanout() was called after we
+		 * pulled this flip from finish_list, in which case the flip
+		 * could be in need of cleanup, but not on cleanup_list.
+		 */
+		if (flip == helper->scanout_flip) {
+			list_del_init(&flip->list);
+		} else {
+			need_cleanup = true;
+			list_move_tail(&flip->list, &driver->cleanup_list);
+		}
+
+		spin_unlock_irq(&driver->lock);
+	}
+
+	if (need_cleanup)
+		queue_work(driver->wq, &driver->cleanup_work);
+}
+
+static bool drm_flip_set_scanout(struct drm_flip_helper *helper,
+				 struct drm_flip *flip)
+{
+	struct drm_flip_driver *driver = helper->driver;
+	struct drm_flip *old = helper->scanout_flip;
+
+	helper->scanout_flip = flip;
+
+	if (old && old->finished)
+		list_move_tail(&old->list, &driver->cleanup_list);
+
+	return old != NULL;
+}
+
+static bool drm_flip_complete(struct drm_flip *flip)
+{
+	struct drm_flip_helper *helper = flip->helper;
+	struct drm_flip_driver *driver = helper->driver;
+	bool need_cleanup = false;
+
+	helper->funcs->complete(flip);
+
+	if (flip->flipped) {
+		if (drm_flip_set_scanout(helper, flip))
+			need_cleanup = true;
+	}
+
+	list_add_tail(&flip->list, &driver->finish_list);
+
+	return need_cleanup;
+}
+
+void drm_flip_helper_init(struct drm_flip_helper *helper,
+			  struct drm_flip_driver *driver,
+			  const struct drm_flip_helper_funcs *funcs)
+{
+	helper->pending_flip = NULL;
+	helper->scanout_flip = NULL;
+	helper->driver = driver;
+	helper->funcs = funcs;
+}
+
+void drm_flip_helper_clear(struct drm_flip_helper *helper)
+{
+	unsigned long flags;
+	struct drm_flip_driver *driver = helper->driver;
+	struct drm_flip *pending_flip;
+	bool need_finish = false;
+	bool need_cleanup = false;
+
+	spin_lock_irqsave(&driver->lock, flags);
+
+	pending_flip = helper->pending_flip;
+
+	if (pending_flip) {
+		BUG_ON(pending_flip->helper != helper);
+
+		need_finish = true;
+
+		if (drm_flip_complete(pending_flip))
+			need_cleanup = true;
+
+		helper->pending_flip = NULL;
+	}
+
+	if (drm_flip_set_scanout(helper, NULL))
+		need_cleanup = true;
+
+	spin_unlock_irqrestore(&driver->lock, flags);
+
+	if (need_finish)
+		queue_work(driver->wq, &driver->finish_work);
+
+	if (need_cleanup)
+		queue_work(driver->wq, &driver->cleanup_work);
+}
+
+void drm_flip_helper_fini(struct drm_flip_helper *helper)
+{
+	struct drm_flip_driver *driver = helper->driver;
+
+	drm_flip_helper_clear(helper);
+
+	flush_work_sync(&driver->finish_work);
+	flush_work_sync(&driver->cleanup_work);
+}
+
+void drm_flip_helper_vblank(struct drm_flip_helper *helper)
+{
+	struct drm_flip_driver *driver = helper->driver;
+	struct drm_flip *pending_flip;
+	unsigned long flags;
+	bool need_finish = false;
+	bool need_cleanup = false;
+
+	spin_lock_irqsave(&driver->lock, flags);
+
+	pending_flip = helper->pending_flip;
+
+	if (pending_flip) {
+		BUG_ON(pending_flip->helper != helper);
+
+		if (helper->funcs->vblank(pending_flip))
+			pending_flip->flipped = true;
+
+		if (pending_flip->flipped) {
+			need_finish = true;
+
+			if (drm_flip_complete(pending_flip))
+				need_cleanup = true;
+
+			helper->pending_flip = NULL;
+		}
+	}
+
+	spin_unlock_irqrestore(&driver->lock, flags);
+
+	if (need_finish)
+		queue_work(driver->wq, &driver->finish_work);
+
+	if (need_cleanup)
+		queue_work(driver->wq, &driver->cleanup_work);
+}
+
+void drm_flip_driver_init(struct drm_flip_driver *driver,
+			  const struct drm_flip_driver_funcs *funcs)
+{
+	spin_lock_init(&driver->lock);
+
+	INIT_LIST_HEAD(&driver->finish_list);
+	INIT_LIST_HEAD(&driver->cleanup_list);
+
+	INIT_WORK(&driver->finish_work, drm_flip_driver_finish);
+	INIT_WORK(&driver->cleanup_work, drm_flip_driver_cleanup);
+
+	driver->funcs = funcs;
+
+	driver->wq = create_singlethread_workqueue("drm_flip");
+}
+
+void drm_flip_driver_fini(struct drm_flip_driver *driver)
+{
+	destroy_workqueue(driver->wq);
+
+	/* All the scheduled flips should be cleaned up by now. */
+	WARN_ON(!list_empty(&driver->finish_list));
+	WARN_ON(!list_empty(&driver->cleanup_list));
+}
+
+void drm_flip_driver_schedule_flips(struct drm_flip_driver *driver,
+				    struct list_head *flips)
+{
+	unsigned long flags;
+	struct drm_flip *flip, *next;
+	bool need_finish = false;
+	bool need_cleanup = false;
+
+	spin_lock_irqsave(&driver->lock, flags);
+
+	list_for_each_entry(flip, flips, list) {
+		struct drm_flip_helper *helper = flip->helper;
+		struct drm_flip *pending_flip = helper->pending_flip;
+
+		if (helper->funcs->flip(flip, pending_flip))
+			pending_flip->flipped = true;
+	}
+
+	if (driver->funcs->flush)
+		driver->funcs->flush(driver);
+
+	/* Complete all flips that got overridden */
+	list_for_each_entry_safe(flip, next, flips, list) {
+		struct drm_flip_helper *helper = flip->helper;
+		struct drm_flip *pending_flip = helper->pending_flip;
+
+		BUG_ON(helper->driver != driver);
+
+		if (pending_flip) {
+			BUG_ON(pending_flip->helper != helper);
+
+			need_finish = true;
+
+			if (drm_flip_complete(pending_flip))
+				need_cleanup = true;
+		}
+
+		list_del_init(&flip->list);
+		helper->pending_flip = flip;
+	}
+
+	spin_unlock_irqrestore(&driver->lock, flags);
+
+	if (need_finish)
+		queue_work(driver->wq, &driver->finish_work);
+
+	if (need_cleanup)
+		queue_work(driver->wq, &driver->cleanup_work);
+}
+
+void drm_flip_driver_prepare_flips(struct drm_flip_driver *driver,
+				   struct list_head *flips)
+{
+	struct drm_flip *flip;
+
+	list_for_each_entry(flip, flips, list) {
+		struct drm_flip_helper *helper = flip->helper;
+
+		if (helper->funcs->prepare)
+			helper->funcs->prepare(flip);
+	}
+}
+
+void drm_flip_driver_complete_flips(struct drm_flip_driver *driver,
+				    struct list_head *flips)
+{
+	unsigned long flags;
+	struct drm_flip *flip, *next;
+	bool need_finish = false;
+	bool need_cleanup = false;
+
+	spin_lock_irqsave(&driver->lock, flags);
+
+	/* first complete all pending flips */
+	list_for_each_entry(flip, flips, list) {
+		struct drm_flip_helper *helper = flip->helper;
+		struct drm_flip *pending_flip = helper->pending_flip;
+
+		BUG_ON(helper->driver != driver);
+
+		if (pending_flip) {
+			BUG_ON(pending_flip->helper != helper);
+
+			need_finish = true;
+
+			if (drm_flip_complete(pending_flip))
+				need_cleanup = true;
+
+			helper->pending_flip = NULL;
+		}
+	}
+
+	/* then complete all new flips as well */
+	list_for_each_entry_safe(flip, next, flips, list) {
+		list_del_init(&flip->list);
+
+		/*
+		 * This is the flip that gets scanned out
+		 * next time the hardware is fired up.
+		 */
+		flip->flipped = true;
+
+		need_finish = true;
+
+		if (drm_flip_complete(flip))
+			need_cleanup = true;
+	}
+
+	spin_unlock_irqrestore(&driver->lock, flags);
+
+	if (need_finish)
+		queue_work(driver->wq, &driver->finish_work);
+
+	if (need_cleanup)
+		queue_work(driver->wq, &driver->cleanup_work);
+}
+
+void drm_flip_init(struct drm_flip *flip,
+		   struct drm_flip_helper *helper)
+{
+	flip->helper = helper;
+	flip->flipped = false;
+	flip->finished = false;
+	INIT_LIST_HEAD(&flip->list);
+}
diff --git a/include/drm/drm_flip.h b/include/drm/drm_flip.h
new file mode 100644
index 0000000..4172d6e
--- /dev/null
+++ b/include/drm/drm_flip.h
@@ -0,0 +1,244 @@ 
+/*
+ * Copyright (C) 2012 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and iated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Authors:
+ * Ville Syrjälä <ville.syrjala@linux.intel.com>
+ */
+
+#ifndef DRM_FLIP_H
+#define DRM_FLIP_H
+
+#include <linux/list.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+
+struct drm_flip;
+struct drm_flip_helper;
+struct drm_flip_driver;
+
+/* Driver callbacks for drm_flip_driver */
+struct drm_flip_driver_funcs {
+	/*
+	 * Optional callback, called after drm_flip_driver_schedule_flips()
+	 * has called drm_flip_helper::flip() for all the provided flips.
+	 * Can be used to:
+	 * - commit the flips atomically to the hardware, if the
+	 *   hardware provides some mechanism to do that.
+	 * - flush posted writes to make sure all the flips have reached
+	 *   the hardware
+	 * Called with drm_flip_driver::lock held.
+	 */
+	void (*flush)(struct drm_flip_driver *driver);
+};
+
+/*
+ * The driver needs one drm_flip_driver to
+ * coordinates the drm_flip mechanism.
+ */
+struct drm_flip_driver {
+	/* protects drm_flip_driver, drm_flip_helper, and drm_flip internals. */
+	spinlock_t lock;
+
+	/* list of drm_flips waiting to be finished, protected by 'lock' */
+	struct list_head finish_list;
+
+	/* list of drm_flips waiting to be cleaned up, protected by 'lock' */
+	struct list_head cleanup_list;
+
+	/* work used to finish the drm_flips */
+	struct work_struct finish_work;
+
+	/* work used to clean up the drm_flips */
+	struct work_struct cleanup_work;
+
+	/* driver provided callback functions */
+	const struct drm_flip_driver_funcs *funcs;
+
+	/* work queue for finish_work and cleanup_work */
+	struct workqueue_struct *wq;
+};
+
+/* Driver callbacks for drm_flip_helper */
+struct drm_flip_helper_funcs {
+	/*
+	 * Optional function to perform heavy but non-timing
+	 * critial preparations for the flip.
+	 * Called from drm_flip_driver_prepare_flips() with
+	 * no extra locks being held.
+	 */
+	void (*prepare)(struct drm_flip *flip);
+	/*
+	 * Instruct the hardware to flip on the next vblank.
+	 * Must return true, iff pending_flip exists, and has
+	 * actually flipped (ie. now being scanned out).
+	 * Otherwise must return false.
+	 * Called with drm_flip_driver::lock held.
+	 */
+	bool (*flip)(struct drm_flip *flip,
+		     struct drm_flip *pending_flip);
+	/*
+	 * Called from drm_flip_helper_vblank() if
+	 * pending_flip exists. Must return true, iff
+	 * pending_flip has actually flipped (ie. now
+	 * being scanned out). Otherwise must return false.
+	 * Called with drm_flip_driver::lock held.
+	 */
+	bool (*vblank)(struct drm_flip *pending_flip);
+
+	/*
+	 * The flip has just occured, or it got overwritten
+	 * by a more recent flip. If the flip occured, it is
+	 * now being scanned out, otherwise it is scheduled
+	 * for cleanup.
+	 * Can be called from drm_flip_driver_schedule_flips(),
+	 * drm_flip_driver_complete_flips(), or from
+	 * drm_flip_helper_vblank().
+	 * Called with drm_flip_driver::lock held.
+	 */
+	void (*complete)(struct drm_flip *flip);
+
+	/*
+	 * Perform finishing steps on the flip. Called from a workqueue
+	 * soon after the flip has completed. The flip's buffer may be
+	 * actively scanned out.
+	 * Called with no locks being held.
+	 */
+	void (*finish)(struct drm_flip *flip);
+
+	/*
+	 * Perform final cleanup on the flip. Called from a workqueue
+	 * after the flip's buffer is no longer being scanned out.
+	 * Called with no locks being held.
+	 */
+	void (*cleanup)(struct drm_flip *flip);
+
+};
+
+/*
+ * The driver needs one drm_flip_helper for each scanout engine it
+ * wants to operate through the drm_flip mechanism.
+ */
+struct drm_flip_helper {
+	/* drm_flip from the previous drm_flip_schedule() call */
+	struct drm_flip *pending_flip;
+	/* drm_flip whose buffer is being scanned out */
+	struct drm_flip *scanout_flip;
+	/* associated drm_flip_driver */
+	struct drm_flip_driver *driver;
+	/* driver provided callback functions */
+	const struct drm_flip_helper_funcs *funcs;
+};
+
+/*
+ * This structure represents a single page flip operation.
+ */
+struct drm_flip {
+	/* associated drm_flip_helper */
+	struct drm_flip_helper *helper;
+	/* has this flip occured? */
+	bool flipped;
+	/* has the finish work been executed for this flip? */
+	bool finished;
+	/* used to keep this flip on various lists */
+	struct list_head list;
+};
+
+/*
+ * Initialize the flip driver.
+ */
+void drm_flip_driver_init(struct drm_flip_driver *driver,
+			  const struct drm_flip_driver_funcs *funcs);
+
+/*
+ * Finalize the flip driver. This will block until all the
+ * pending finish and cleanup work has been completed.
+ */
+void drm_flip_driver_fini(struct drm_flip_driver *driver);
+
+/*
+ * Initialize flip helper.
+ */
+void drm_flip_helper_init(struct drm_flip_helper *helper,
+			  struct drm_flip_driver *driver,
+			  const struct drm_flip_helper_funcs *funcs);
+
+/*
+ * Clear flip helper state. This will forcefully complete the
+ * helper's pending flip (if any).
+ */
+void drm_flip_helper_clear(struct drm_flip_helper *helper);
+
+/*
+ * Finalize the flip helper. This will forcefully complete the
+ * helper's pending flip (if any), and wait for the finish and
+ * cleanup works to finish.
+ */
+void drm_flip_helper_fini(struct drm_flip_helper *helper);
+
+/*
+ * Call this from the driver's vblank handler for the scanout engine
+ * associated with this helper.
+ */
+void drm_flip_helper_vblank(struct drm_flip_helper *helper);
+
+/*
+ * This will call drm_flip_helper::prepare() (if provided) for all the
+ * drm_flips on the list. The purpose is to perform any non-timing critical
+ * preparation steps for the flips before taking locks or disabling interrupts.
+ */
+void drm_flip_driver_prepare_flips(struct drm_flip_driver *driver,
+				   struct list_head *flips);
+
+/*
+ * Schedule the flips on the list to occur on the next vblank.
+ *
+ * This will call drm_flip_helper::flip() for all the drm_flips on the list.
+ * It will then call drm_flip_driver::flush(), after which it will complete
+ * any pending_flip that got overridden by the new flips.
+ *
+ * Unless the hardware provides some mechanism to synchronize the flips, the
+ * time spent until drm_flip_driver::flush() is timing critical and the driver
+ * must somehow make sure it can complete the operation in a seemingly atomic
+ * fashion.
+ */
+void drm_flip_driver_schedule_flips(struct drm_flip_driver *driver,
+				    struct list_head *flips);
+
+/*
+ * This will complete any pending_flip and also all the flips
+ * on the provided list (in that order).
+ *
+ * Call this instead of drm_flip_driver_schedule_flips()
+ * eg. if the hardware powered down, and you just want to keep
+ * the drm_flip mechanim's state consistent w/o waking up the
+ * hardware.
+ */
+void drm_flip_driver_complete_flips(struct drm_flip_driver *driver,
+				    struct list_head *flips);
+
+/*
+ * Initialize the flip structure members.
+ */
+void drm_flip_init(struct drm_flip *flip,
+		   struct drm_flip_helper *helper);
+
+#endif