![Lyude Paul](/assets/img/avatar_default.png)
Add some kind of vblank workers. The interface is similar to regular delayed works, and is mostly based off kthread_work. It allows for scheduling delayed works that execute once a particular vblank sequence has passed. It also allows for accurate flushing of scheduled vblank works - in that flushing waits for both the vblank sequence and job execution to complete, or for the work to get cancelled - whichever comes first. Whatever hardware programming we do in the work must be fast (must at least complete during the vblank or scanout period, sometimes during the first few scanlines of the vblank). As such we use a high-priority per-CRTC thread to accomplish this. Changes since v7: * Stuff drm_vblank_internal.h and drm_vblank_work_internal.h contents into drm_internal.h * Get rid of unnecessary spinlock in drm_crtc_vblank_on() * Remove !vblank->worker check * Grab vbl_lock in drm_vblank_work_schedule() * Mention self-rearming work items in drm_vblank_work_schedule() kdocs * Return 1 from drm_vblank_work_schedule() if the work was scheduled successfully, 0 or error code otherwise * Use drm_dbg_core() instead of DRM_DEV_ERROR() in drm_vblank_work_schedule() * Remove vblank->worker checks in drm_vblank_destroy_worker() and drm_vblank_flush_worker() Changes since v6: * Get rid of ->pending and seqcounts, and implement flushing through simpler means - danvet * Get rid of work_lock, just use drm_device->event_lock * Move drm_vblank_work item cleanup into drm_crtc_vblank_off() so that we ensure that all vblank work has finished before disabling vblanks * Add checks into drm_crtc_vblank_reset() so we yell if it gets called while there's vblank workers active * Grab event_lock in both drm_crtc_vblank_on()/drm_crtc_vblank_off(), the main reason for this is so that other threads calling drm_vblank_work_schedule() are blocked from attempting to schedule while we're in the middle of enabling/disabling vblanks. * Move drm_handle_vblank_works() call below drm_handle_vblank_events() * Simplify drm_vblank_work_cancel_sync() * Fix drm_vblank_work_cancel_sync() documentation * Move wake_up_all() calls out of spinlock where we can. The only one I left was the call to wake_up_all() in drm_vblank_handle_works() as this seemed like it made more sense just living in that function (which is all technically under lock) * Move drm_vblank_work related functions into their own source files * Add drm_vblank_internal.h so we can export some functions we don't want drivers using, but that we do need to use in drm_vblank_work.c * Add a bunch of documentation Changes since v4: * Get rid of kthread interfaces we tried adding and move all of the locking into drm_vblank.c. For implementing drm_vblank_work_flush(), we now use a wait_queue and sequence counters in order to differentiate between multiple work item executions. * Get rid of drm_vblank_work_cancel() - this would have been pretty difficult to actually reimplement and it occurred to me that neither nouveau or i915 are even planning to use this function. Since there's also no async cancel function for most of the work interfaces in the kernel, it seems a bit unnecessary anyway. * Get rid of to_drm_vblank_work() since we now are also able to just pass the struct drm_vblank_work to work item callbacks anyway Changes since v3: * Use our own spinlocks, don't integrate so tightly with kthread_works Changes since v2: * Use kthread_workers instead of reinventing the wheel. Cc: Tejun Heo <tj@kernel.org> Cc: dri-devel@lists.freedesktop.org Cc: nouveau@lists.freedesktop.org Reviewed-by: Daniel Vetter <daniel.vetter@ffwll.ch> Co-developed-by: Ville Syrjälä <ville.syrjala@linux.intel.com> Signed-off-by: Lyude Paul <lyude@redhat.com> Acked-by: Dave Airlie <airlied@gmail.com> Link: https://patchwork.freedesktop.org/patch/msgid/20200627194657.156514-4-lyude@redhat.com
282 lines
9.1 KiB
C
282 lines
9.1 KiB
C
/*
|
|
* Copyright 2016 Intel Corp.
|
|
*
|
|
* 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
|
|
* VA LINUX SYSTEMS AND/OR ITS SUPPLIERS 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.
|
|
*/
|
|
|
|
#ifndef _DRM_VBLANK_H_
|
|
#define _DRM_VBLANK_H_
|
|
|
|
#include <linux/seqlock.h>
|
|
#include <linux/idr.h>
|
|
#include <linux/poll.h>
|
|
#include <linux/kthread.h>
|
|
|
|
#include <drm/drm_file.h>
|
|
#include <drm/drm_modes.h>
|
|
|
|
struct drm_device;
|
|
struct drm_crtc;
|
|
struct drm_vblank_work;
|
|
|
|
/**
|
|
* struct drm_pending_vblank_event - pending vblank event tracking
|
|
*/
|
|
struct drm_pending_vblank_event {
|
|
/**
|
|
* @base: Base structure for tracking pending DRM events.
|
|
*/
|
|
struct drm_pending_event base;
|
|
/**
|
|
* @pipe: drm_crtc_index() of the &drm_crtc this event is for.
|
|
*/
|
|
unsigned int pipe;
|
|
/**
|
|
* @sequence: frame event should be triggered at
|
|
*/
|
|
u64 sequence;
|
|
/**
|
|
* @event: Actual event which will be sent to userspace.
|
|
*/
|
|
union {
|
|
/**
|
|
* @event.base: DRM event base class.
|
|
*/
|
|
struct drm_event base;
|
|
|
|
/**
|
|
* @event.vbl:
|
|
*
|
|
* Event payload for vblank events, requested through
|
|
* either the MODE_PAGE_FLIP or MODE_ATOMIC IOCTL. Also
|
|
* generated by the legacy WAIT_VBLANK IOCTL, but new userspace
|
|
* should use MODE_QUEUE_SEQUENCE and &event.seq instead.
|
|
*/
|
|
struct drm_event_vblank vbl;
|
|
|
|
/**
|
|
* @event.seq: Event payload for the MODE_QUEUEU_SEQUENCE IOCTL.
|
|
*/
|
|
struct drm_event_crtc_sequence seq;
|
|
} event;
|
|
};
|
|
|
|
/**
|
|
* struct drm_vblank_crtc - vblank tracking for a CRTC
|
|
*
|
|
* This structure tracks the vblank state for one CRTC.
|
|
*
|
|
* Note that for historical reasons - the vblank handling code is still shared
|
|
* with legacy/non-kms drivers - this is a free-standing structure not directly
|
|
* connected to &struct drm_crtc. But all public interface functions are taking
|
|
* a &struct drm_crtc to hide this implementation detail.
|
|
*/
|
|
struct drm_vblank_crtc {
|
|
/**
|
|
* @dev: Pointer to the &drm_device.
|
|
*/
|
|
struct drm_device *dev;
|
|
/**
|
|
* @queue: Wait queue for vblank waiters.
|
|
*/
|
|
wait_queue_head_t queue;
|
|
/**
|
|
* @disable_timer: Disable timer for the delayed vblank disabling
|
|
* hysteresis logic. Vblank disabling is controlled through the
|
|
* drm_vblank_offdelay module option and the setting of the
|
|
* &drm_device.max_vblank_count value.
|
|
*/
|
|
struct timer_list disable_timer;
|
|
|
|
/**
|
|
* @seqlock: Protect vblank count and time.
|
|
*/
|
|
seqlock_t seqlock;
|
|
|
|
/**
|
|
* @count:
|
|
*
|
|
* Current software vblank counter.
|
|
*
|
|
* Note that for a given vblank counter value drm_crtc_handle_vblank()
|
|
* and drm_crtc_vblank_count() or drm_crtc_vblank_count_and_time()
|
|
* provide a barrier: Any writes done before calling
|
|
* drm_crtc_handle_vblank() will be visible to callers of the later
|
|
* functions, iff the vblank count is the same or a later one.
|
|
*
|
|
* IMPORTANT: This guarantee requires barriers, therefor never access
|
|
* this field directly. Use drm_crtc_vblank_count() instead.
|
|
*/
|
|
atomic64_t count;
|
|
/**
|
|
* @time: Vblank timestamp corresponding to @count.
|
|
*/
|
|
ktime_t time;
|
|
|
|
/**
|
|
* @refcount: Number of users/waiters of the vblank interrupt. Only when
|
|
* this refcount reaches 0 can the hardware interrupt be disabled using
|
|
* @disable_timer.
|
|
*/
|
|
atomic_t refcount;
|
|
/**
|
|
* @last: Protected by &drm_device.vbl_lock, used for wraparound handling.
|
|
*/
|
|
u32 last;
|
|
/**
|
|
* @max_vblank_count:
|
|
*
|
|
* Maximum value of the vblank registers for this crtc. This value +1
|
|
* will result in a wrap-around of the vblank register. It is used
|
|
* by the vblank core to handle wrap-arounds.
|
|
*
|
|
* If set to zero the vblank core will try to guess the elapsed vblanks
|
|
* between times when the vblank interrupt is disabled through
|
|
* high-precision timestamps. That approach is suffering from small
|
|
* races and imprecision over longer time periods, hence exposing a
|
|
* hardware vblank counter is always recommended.
|
|
*
|
|
* This is the runtime configurable per-crtc maximum set through
|
|
* drm_crtc_set_max_vblank_count(). If this is used the driver
|
|
* must leave the device wide &drm_device.max_vblank_count at zero.
|
|
*
|
|
* If non-zero, &drm_crtc_funcs.get_vblank_counter must be set.
|
|
*/
|
|
u32 max_vblank_count;
|
|
/**
|
|
* @inmodeset: Tracks whether the vblank is disabled due to a modeset.
|
|
* For legacy driver bit 2 additionally tracks whether an additional
|
|
* temporary vblank reference has been acquired to paper over the
|
|
* hardware counter resetting/jumping. KMS drivers should instead just
|
|
* call drm_crtc_vblank_off() and drm_crtc_vblank_on(), which explicitly
|
|
* save and restore the vblank count.
|
|
*/
|
|
unsigned int inmodeset;
|
|
/**
|
|
* @pipe: drm_crtc_index() of the &drm_crtc corresponding to this
|
|
* structure.
|
|
*/
|
|
unsigned int pipe;
|
|
/**
|
|
* @framedur_ns: Frame/Field duration in ns, used by
|
|
* drm_crtc_vblank_helper_get_vblank_timestamp() and computed by
|
|
* drm_calc_timestamping_constants().
|
|
*/
|
|
int framedur_ns;
|
|
/**
|
|
* @linedur_ns: Line duration in ns, used by
|
|
* drm_crtc_vblank_helper_get_vblank_timestamp() and computed by
|
|
* drm_calc_timestamping_constants().
|
|
*/
|
|
int linedur_ns;
|
|
|
|
/**
|
|
* @hwmode:
|
|
*
|
|
* Cache of the current hardware display mode. Only valid when @enabled
|
|
* is set. This is used by helpers like
|
|
* drm_crtc_vblank_helper_get_vblank_timestamp(). We can't just access
|
|
* the hardware mode by e.g. looking at &drm_crtc_state.adjusted_mode,
|
|
* because that one is really hard to get from interrupt context.
|
|
*/
|
|
struct drm_display_mode hwmode;
|
|
|
|
/**
|
|
* @enabled: Tracks the enabling state of the corresponding &drm_crtc to
|
|
* avoid double-disabling and hence corrupting saved state. Needed by
|
|
* drivers not using atomic KMS, since those might go through their CRTC
|
|
* disabling functions multiple times.
|
|
*/
|
|
bool enabled;
|
|
|
|
/**
|
|
* @worker: The &kthread_worker used for executing vblank works.
|
|
*/
|
|
struct kthread_worker *worker;
|
|
|
|
/**
|
|
* @pending_work: A list of scheduled &drm_vblank_work items that are
|
|
* waiting for a future vblank.
|
|
*/
|
|
struct list_head pending_work;
|
|
|
|
/**
|
|
* @work_wait_queue: The wait queue used for signaling that a
|
|
* &drm_vblank_work item has either finished executing, or was
|
|
* cancelled.
|
|
*/
|
|
wait_queue_head_t work_wait_queue;
|
|
};
|
|
|
|
int drm_vblank_init(struct drm_device *dev, unsigned int num_crtcs);
|
|
bool drm_dev_has_vblank(const struct drm_device *dev);
|
|
u64 drm_crtc_vblank_count(struct drm_crtc *crtc);
|
|
u64 drm_crtc_vblank_count_and_time(struct drm_crtc *crtc,
|
|
ktime_t *vblanktime);
|
|
void drm_crtc_send_vblank_event(struct drm_crtc *crtc,
|
|
struct drm_pending_vblank_event *e);
|
|
void drm_crtc_arm_vblank_event(struct drm_crtc *crtc,
|
|
struct drm_pending_vblank_event *e);
|
|
void drm_vblank_set_event(struct drm_pending_vblank_event *e,
|
|
u64 *seq,
|
|
ktime_t *now);
|
|
bool drm_handle_vblank(struct drm_device *dev, unsigned int pipe);
|
|
bool drm_crtc_handle_vblank(struct drm_crtc *crtc);
|
|
int drm_crtc_vblank_get(struct drm_crtc *crtc);
|
|
void drm_crtc_vblank_put(struct drm_crtc *crtc);
|
|
void drm_wait_one_vblank(struct drm_device *dev, unsigned int pipe);
|
|
void drm_crtc_wait_one_vblank(struct drm_crtc *crtc);
|
|
void drm_crtc_vblank_off(struct drm_crtc *crtc);
|
|
void drm_crtc_vblank_reset(struct drm_crtc *crtc);
|
|
void drm_crtc_vblank_on(struct drm_crtc *crtc);
|
|
u64 drm_crtc_accurate_vblank_count(struct drm_crtc *crtc);
|
|
void drm_vblank_restore(struct drm_device *dev, unsigned int pipe);
|
|
void drm_crtc_vblank_restore(struct drm_crtc *crtc);
|
|
|
|
void drm_calc_timestamping_constants(struct drm_crtc *crtc,
|
|
const struct drm_display_mode *mode);
|
|
wait_queue_head_t *drm_crtc_vblank_waitqueue(struct drm_crtc *crtc);
|
|
void drm_crtc_set_max_vblank_count(struct drm_crtc *crtc,
|
|
u32 max_vblank_count);
|
|
|
|
/*
|
|
* Helpers for struct drm_crtc_funcs
|
|
*/
|
|
|
|
typedef bool (*drm_vblank_get_scanout_position_func)(struct drm_crtc *crtc,
|
|
bool in_vblank_irq,
|
|
int *vpos, int *hpos,
|
|
ktime_t *stime,
|
|
ktime_t *etime,
|
|
const struct drm_display_mode *mode);
|
|
|
|
bool
|
|
drm_crtc_vblank_helper_get_vblank_timestamp_internal(struct drm_crtc *crtc,
|
|
int *max_error,
|
|
ktime_t *vblank_time,
|
|
bool in_vblank_irq,
|
|
drm_vblank_get_scanout_position_func get_scanout_position);
|
|
bool drm_crtc_vblank_helper_get_vblank_timestamp(struct drm_crtc *crtc,
|
|
int *max_error,
|
|
ktime_t *vblank_time,
|
|
bool in_vblank_irq);
|
|
|
|
#endif
|