When trying to fix the hardware programming in intel/display, I had to take all the vblank locks with local_irqs_disabled(). This required converting the entire vblank code to raw spinlocks.
In the alternative approach, do all preparations in advance, and only enable the vblank_event with interrupts disabled, this requires only a simple write and prevents a complete re-architecture of the code. Signed-off-by: Maarten Lankhorst <[email protected]> --- drivers/gpu/drm/drm_vblank.c | 61 +++++++++++++++++++++++++++++++++++- include/drm/drm_vblank.h | 14 ++++++++- 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/drm_vblank.c b/drivers/gpu/drm/drm_vblank.c index 42fe11cc139b9..1a84a69e99810 100644 --- a/drivers/gpu/drm/drm_vblank.c +++ b/drivers/gpu/drm/drm_vblank.c @@ -1118,12 +1118,68 @@ void drm_crtc_arm_vblank_event(struct drm_crtc *crtc, assert_spin_locked(&dev->event_lock); + WARN_ON(e->postponed); e->pipe = pipe; e->sequence = drm_crtc_accurate_vblank_count(crtc) + 1; list_add_tail(&e->base.link, &dev->vblank_event_list); } EXPORT_SYMBOL(drm_crtc_arm_vblank_event); +/** + * drm_crtc_prepare_arm_vblank_event - arm vblank event *before* pageflip. + * @crtc: the source CRTC of the vblank event + * @e: the event to send + * + * See drm_crtc_arm_vblank_event(). This function is a 2-stage version of + * that call. This function is called *BEFORE* programming the hardware. + * + * After programming, call drm_crtc_arm_prepared_vblank_event() and the + * event will be scheduled on the next vblank. + * + * This is mainly useful for code that has to run on PREEMPT_RT kernels, + * with interrupts disabled, since all vblank spinlocks are converted to + * rtmutexes, and code running with irqs disabled cannot take any vblank lock. + * + * It also increases determinism for any hardware + * programming, since no vblank related locks are taking when arming. + */ +void drm_crtc_prepare_arm_vblank_event(struct drm_crtc *crtc, + struct drm_pending_vblank_event *e) +{ + drm_crtc_arm_vblank_event(crtc, e); + + /* Set the flag, so that the event is not fired yet */ + e->postponed = true; +} +EXPORT_SYMBOL(drm_crtc_prepare_arm_vblank_event); + +/** + * drm_crtc_arm_prepared_vblank_event - arm prepared vblank event *after* pageflip. + * @crtc: the source CRTC of the vblank event + * @e: the event to send + * + * See drm_crtc_prepare_arm_vblank_event(). This function is a 2-stage version of + * that call. This function is called directly *AFTER* programming the hardware. + * + * Before this function is called, drm_crtc_prepare_arm_vblank_event() should be + * called instead. + * + * This is mainly useful for code that has to run on PREEMPT_RT kernels, + * with interrupts disabled, since all vblank spinlocks are converted to + * rtmutexes, and code running with irqs disabled cannot take any vblank lock. + * + * It also increases determinism for any hardware + * programming, since no vblank related locks are taking when arming. + */ +void drm_crtc_arm_prepared_vblank_event(struct drm_pending_vblank_event *e) +{ + WARN_ON(!e->postponed); + + /* remove the flag to be processed as a normal event */ + WRITE_ONCE(e->postponed, false); +} +EXPORT_SYMBOL(drm_crtc_arm_prepared_vblank_event); + /** * drm_crtc_send_vblank_event - helper to send vblank event after pageflip * @crtc: the source CRTC of the vblank event @@ -1381,6 +1437,8 @@ void drm_crtc_vblank_off(struct drm_crtc *crtc) list_for_each_entry_safe(e, t, &dev->vblank_event_list, base.link) { if (e->pipe != pipe) continue; + + WARN_ON(e->postponed); drm_dbg_core(dev, "Sending premature vblank event on disable: " "wanted %llu, current %llu\n", e->sequence, seq); @@ -1886,7 +1944,8 @@ static void drm_handle_vblank_events(struct drm_device *dev, unsigned int pipe) seq = drm_vblank_count_and_time(dev, pipe, &now); list_for_each_entry_safe(e, t, &dev->vblank_event_list, base.link) { - if (e->pipe != pipe) + /* Matches WRITE_ONCE in drm_crtc_arm_prepared_vblank_event() */ + if (e->pipe != pipe || READ_ONCE(e->postponed)) continue; if (!drm_vblank_passed(seq, e->sequence)) continue; diff --git a/include/drm/drm_vblank.h b/include/drm/drm_vblank.h index 2fcef9c0f5b1b..956d5621eb7f9 100644 --- a/include/drm/drm_vblank.h +++ b/include/drm/drm_vblank.h @@ -53,6 +53,13 @@ struct drm_pending_vblank_event { * @sequence: frame event should be triggered at */ u64 sequence; + + /** + * @postponed: whether drm_crtc_prepare_arm_vblank_event() is called, + * and drm_crtc_arm_prepared_vblank_event has yet to be called to arm. + */ + bool postponed; + /** * @event: Actual event which will be sent to userspace. */ @@ -294,7 +301,12 @@ int drm_crtc_next_vblank_start(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); + struct drm_pending_vblank_event *e); + +void drm_crtc_prepare_arm_vblank_event(struct drm_crtc *crtc, + struct drm_pending_vblank_event *e); +void drm_crtc_arm_prepared_vblank_event(struct drm_pending_vblank_event *e); + void drm_vblank_set_event(struct drm_pending_vblank_event *e, u64 *seq, ktime_t *now); -- 2.51.0
