From: Ville Syrj?l? <ville.syrjala at 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 at 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 --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 6f58c81..f2965de 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 at 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 at 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 -- 1.7.8.6