Commit 74afeb81 authored by Thomas Zimmermann's avatar Thomas Zimmermann
Browse files

drm/vblank: Add vblank timer



The vblank timer simulates a vblank interrupt for hardware without
support. Rate-limits the display update frequency.

DRM drivers for hardware without vblank support apply display updates
ASAP. A vblank event informs DRM clients of the completed update.
Userspace compositors immediately schedule the next update, which
creates significant load on virtualization outputs. Display updates
are usually fast on virtualization outputs, as their framebuffers are
in regular system memory and there's no hardware vblank interrupt to
throttle the update rate.

The vblank timer is a HR timer that signals the vblank in software.
It limits the update frequency of a DRM driver similar to a hardware
vblank interrupt. The timer is not synchronized to the actual vblank
interval of the display.

The code has been adopted from vkms, which added the funtionality
in commit 3a070992 ("drm/vkms: Add vblank events simulated by
hrtimers").

The new implementation is part of the existing vblank support,
which sets up the timer automatically. Drivers only have to start
and cancel the vblank timer as part of enabling and disabling the
CRTC. The new vblank helper library provides callbacks for struct
drm_crtc_funcs.

The standard way for handling vblank is to call drm_crtc_handle_vblank().
Drivers that require additional processing, such as vkms, can init
handle_vblank_timeout in struct drm_crtc_helper_funcs to refer to
their timeout handler.

There's a possible deadlock between drm_crtc_handle_vblank() and
hrtimer_cancel(). [1] The implementation avoids to call hrtimer_cancel()
directly and instead signals to the timer function to not restart
itself.

v4:
- fix possible race condition between timeout and atomic commit (Michael)
v3:
- avoid deadlock when cancelling timer (Ville, Lyude)
v2:
- implement vblank timer entirely in vblank helpers
- downgrade overrun warning to debug
- fix docs

Signed-off-by: default avatarThomas Zimmermann <tzimmermann@suse.de>
Tested-by: default avatarLouis Chauvet <louis.chauvet@bootlin.com>
Reviewed-by: default avatarLouis Chauvet <louis.chauvet@bootlin.com>
Reviewed-by: default avatarJavier Martinez Canillas <javierm@redhat.com>
Tested-by: default avatarMichael Kelley <mhklinux@outlook.com>
Link: https://lore.kernel.org/all/20250510094757.4174662-1-zengheng4@huawei.com/ # [1]
Link: https://lore.kernel.org/r/20250916083816.30275-2-tzimmermann@suse.de
parent b83c30ac
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -92,6 +92,18 @@ GEM Atomic Helper Reference
.. kernel-doc:: drivers/gpu/drm/drm_gem_atomic_helper.c
   :export:

VBLANK Helper Reference
-----------------------

.. kernel-doc:: drivers/gpu/drm/drm_vblank_helper.c
   :doc: overview

.. kernel-doc:: include/drm/drm_vblank_helper.h
   :internal:

.. kernel-doc:: drivers/gpu/drm/drm_vblank_helper.c
   :export:

Simple KMS Helper Reference
===========================

+2 −1
Original line number Diff line number Diff line
@@ -150,7 +150,8 @@ drm_kms_helper-y := \
	drm_plane_helper.o \
	drm_probe_helper.o \
	drm_self_refresh_helper.o \
	drm_simple_kms_helper.o
	drm_simple_kms_helper.o \
	drm_vblank_helper.o
drm_kms_helper-$(CONFIG_DRM_PANEL_BRIDGE) += bridge/panel.o
drm_kms_helper-$(CONFIG_DRM_FBDEV_EMULATION) += drm_fb_helper.o
obj-$(CONFIG_DRM_KMS_HELPER) += drm_kms_helper.o
+170 −2
Original line number Diff line number Diff line
@@ -136,8 +136,17 @@
 * vblanks after a timer has expired, which can be configured through the
 * ``vblankoffdelay`` module parameter.
 *
 * Drivers for hardware without support for vertical-blanking interrupts
 * must not call drm_vblank_init(). For such drivers, atomic helpers will
 * Drivers for hardware without support for vertical-blanking interrupts can
 * use DRM vblank timers to send vblank events at the rate of the current
 * display mode's refresh. While not synchronized to the hardware's
 * vertical-blanking regions, the timer helps DRM clients and compositors to
 * adapt their update cycle to the display output. Drivers should set up
 * vblanking as usual, but call drm_crtc_vblank_start_timer() and
 * drm_crtc_vblank_cancel_timer() as part of their atomic mode setting.
 * See also DRM vblank helpers for more information.
 *
 * Drivers without support for vertical-blanking interrupts nor timers must
 * not call drm_vblank_init(). For these drivers, atomic helpers will
 * automatically generate fake vblank events as part of the display update.
 * This functionality also can be controlled by the driver by enabling and
 * disabling struct drm_crtc_state.no_vblank.
@@ -508,6 +517,9 @@ static void drm_vblank_init_release(struct drm_device *dev, void *ptr)
	drm_WARN_ON(dev, READ_ONCE(vblank->enabled) &&
		    drm_core_check_feature(dev, DRIVER_MODESET));

	if (vblank->vblank_timer.crtc)
		hrtimer_cancel(&vblank->vblank_timer.timer);

	drm_vblank_destroy_worker(vblank);
	timer_delete_sync(&vblank->disable_timer);
}
@@ -2162,3 +2174,159 @@ int drm_crtc_queue_sequence_ioctl(struct drm_device *dev, void *data,
	return ret;
}

/*
 * VBLANK timer
 */

static enum hrtimer_restart drm_vblank_timer_function(struct hrtimer *timer)
{
	struct drm_vblank_crtc_timer *vtimer =
		container_of(timer, struct drm_vblank_crtc_timer, timer);
	struct drm_crtc *crtc = vtimer->crtc;
	const struct drm_crtc_helper_funcs *crtc_funcs = crtc->helper_private;
	struct drm_device *dev = crtc->dev;
	unsigned long flags;
	ktime_t interval;
	u64 ret_overrun;
	bool succ;

	spin_lock_irqsave(&vtimer->interval_lock, flags);
	interval = vtimer->interval;
	spin_unlock_irqrestore(&vtimer->interval_lock, flags);

	if (!interval)
		return HRTIMER_NORESTART;

	ret_overrun = hrtimer_forward_now(&vtimer->timer, interval);
	if (ret_overrun != 1)
		drm_dbg_vbl(dev, "vblank timer overrun\n");

	if (crtc_funcs->handle_vblank_timeout)
		succ = crtc_funcs->handle_vblank_timeout(crtc);
	else
		succ = drm_crtc_handle_vblank(crtc);
	if (!succ)
		return HRTIMER_NORESTART;

	return HRTIMER_RESTART;
}

/**
 * drm_crtc_vblank_start_timer - Starts the vblank timer on the given CRTC
 * @crtc: the CRTC
 *
 * Drivers should call this function from their CRTC's enable_vblank
 * function to start a vblank timer. The timer will fire after the duration
 * of a full frame. drm_crtc_vblank_cancel_timer() disables a running timer.
 *
 * Returns:
 * 0 on success, or a negative errno code otherwise.
 */
int drm_crtc_vblank_start_timer(struct drm_crtc *crtc)
{
	struct drm_vblank_crtc *vblank = drm_crtc_vblank_crtc(crtc);
	struct drm_vblank_crtc_timer *vtimer = &vblank->vblank_timer;
	unsigned long flags;

	if (!vtimer->crtc) {
		/*
		 * Set up the data structures on the first invocation.
		 */
		vtimer->crtc = crtc;
		spin_lock_init(&vtimer->interval_lock);
		hrtimer_setup(&vtimer->timer, drm_vblank_timer_function,
			      CLOCK_MONOTONIC, HRTIMER_MODE_REL);
	} else {
		/*
		 * Timer should not be active. If it is, wait for the
		 * previous cancel operations to finish.
		 */
		while (hrtimer_active(&vtimer->timer))
			hrtimer_try_to_cancel(&vtimer->timer);
	}

	drm_calc_timestamping_constants(crtc, &crtc->mode);

	spin_lock_irqsave(&vtimer->interval_lock, flags);
	vtimer->interval = ns_to_ktime(vblank->framedur_ns);
	spin_unlock_irqrestore(&vtimer->interval_lock, flags);

	hrtimer_start(&vtimer->timer, vtimer->interval, HRTIMER_MODE_REL);

	return 0;
}
EXPORT_SYMBOL(drm_crtc_vblank_start_timer);

/**
 * drm_crtc_vblank_start_timer - Cancels the given CRTC's vblank timer
 * @crtc: the CRTC
 *
 * Drivers should call this function from their CRTC's disable_vblank
 * function to stop a vblank timer.
 */
void drm_crtc_vblank_cancel_timer(struct drm_crtc *crtc)
{
	struct drm_vblank_crtc *vblank = drm_crtc_vblank_crtc(crtc);
	struct drm_vblank_crtc_timer *vtimer = &vblank->vblank_timer;
	unsigned long flags;

	/*
	 * Calling hrtimer_cancel() can result in a deadlock with DRM's
	 * vblank_time_lime_lock and hrtimers' softirq_expiry_lock. So
	 * clear interval and indicate cancellation. The timer function
	 * will cancel itself on the next invocation.
	 */

	spin_lock_irqsave(&vtimer->interval_lock, flags);
	vtimer->interval = 0;
	spin_unlock_irqrestore(&vtimer->interval_lock, flags);

	hrtimer_try_to_cancel(&vtimer->timer);
}
EXPORT_SYMBOL(drm_crtc_vblank_cancel_timer);

/**
 * drm_crtc_vblank_get_vblank_timeout - Returns the vblank timeout
 * @crtc: The CRTC
 * @vblank_time: Returns the next vblank timestamp
 *
 * The helper drm_crtc_vblank_get_vblank_timeout() returns the next vblank
 * timestamp of the CRTC's vblank timer according to the timer's expiry
 * time.
 */
void drm_crtc_vblank_get_vblank_timeout(struct drm_crtc *crtc, ktime_t *vblank_time)
{
	struct drm_vblank_crtc *vblank = drm_crtc_vblank_crtc(crtc);
	struct drm_vblank_crtc_timer *vtimer = &vblank->vblank_timer;
	u64 cur_count;
	ktime_t cur_time;

	if (!READ_ONCE(vblank->enabled)) {
		*vblank_time = ktime_get();
		return;
	}

	/*
	 * A concurrent vblank timeout could update the expires field before
	 * we compare it with the vblank time. Hence we'd compare the old
	 * expiry time to the new vblank time; deducing the timer had already
	 * expired. Reread until we get consistent values from both fields.
	 */
	do {
		cur_count = drm_crtc_vblank_count_and_time(crtc, &cur_time);
		*vblank_time = READ_ONCE(vtimer->timer.node.expires);
	} while (cur_count != drm_crtc_vblank_count_and_time(crtc, &cur_time));

	if (drm_WARN_ON(crtc->dev, !ktime_compare(*vblank_time, cur_time)))
		return; /* Already expired */

	/*
	 * To prevent races we roll the hrtimer forward before we do any
	 * interrupt processing - this is how real hw works (the interrupt
	 * is only generated after all the vblank registers are updated)
	 * and what the vblank core expects. Therefore we need to always
	 * correct the timestamp by one frame.
	 */
	*vblank_time = ktime_sub(*vblank_time, vtimer->interval);
}
EXPORT_SYMBOL(drm_crtc_vblank_get_vblank_timeout);
+96 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: MIT

#include <drm/drm_crtc.h>
#include <drm/drm_managed.h>
#include <drm/drm_modeset_helper_vtables.h>
#include <drm/drm_print.h>
#include <drm/drm_vblank.h>
#include <drm/drm_vblank_helper.h>

/**
 * DOC: overview
 *
 * The vblank helper library provides functions for supporting vertical
 * blanking in DRM drivers.
 *
 * For vblank timers, several callback implementations are available.
 * Drivers enable support for vblank timers by setting the vblank callbacks
 * in struct &drm_crtc_funcs to the helpers provided by this library. The
 * initializer macro DRM_CRTC_VBLANK_TIMER_FUNCS does this conveniently.
 *
 * Once the driver enables vblank support with drm_vblank_init(), each
 * CRTC's vblank timer fires according to the programmed display mode. By
 * default, the vblank timer invokes drm_crtc_handle_vblank(). Drivers with
 * more specific requirements can set their own handler function in
 * struct &drm_crtc_helper_funcs.handle_vblank_timeout.
 */

/*
 * VBLANK timer
 */

/**
 * drm_crtc_vblank_helper_enable_vblank_timer - Implements struct &drm_crtc_funcs.enable_vblank
 * @crtc: The CRTC
 *
 * The helper drm_crtc_vblank_helper_enable_vblank_timer() implements
 * enable_vblank of struct drm_crtc_helper_funcs for CRTCs that require
 * a VBLANK timer. It sets up the timer on the first invocation. The
 * started timer expires after the current frame duration. See struct
 * &drm_vblank_crtc.framedur_ns.
 *
 * See also struct &drm_crtc_helper_funcs.enable_vblank.
 *
 * Returns:
 * 0 on success, or a negative errno code otherwise.
 */
int drm_crtc_vblank_helper_enable_vblank_timer(struct drm_crtc *crtc)
{
	return drm_crtc_vblank_start_timer(crtc);
}
EXPORT_SYMBOL(drm_crtc_vblank_helper_enable_vblank_timer);

/**
 * drm_crtc_vblank_helper_disable_vblank_timer - Implements struct &drm_crtc_funcs.disable_vblank
 * @crtc: The CRTC
 *
 * The helper drm_crtc_vblank_helper_disable_vblank_timer() implements
 * disable_vblank of struct drm_crtc_funcs for CRTCs that require a
 * VBLANK timer.
 *
 * See also struct &drm_crtc_helper_funcs.disable_vblank.
 */
void drm_crtc_vblank_helper_disable_vblank_timer(struct drm_crtc *crtc)
{
	drm_crtc_vblank_cancel_timer(crtc);
}
EXPORT_SYMBOL(drm_crtc_vblank_helper_disable_vblank_timer);

/**
 * drm_crtc_vblank_helper_get_vblank_timestamp_from_timer -
 *	Implements struct &drm_crtc_funcs.get_vblank_timestamp
 * @crtc: The CRTC
 * @max_error: Maximum acceptable error
 * @vblank_time: Returns the next vblank timestamp
 * @in_vblank_irq: True is called from drm_crtc_handle_vblank()
 *
 * The helper drm_crtc_helper_get_vblank_timestamp_from_timer() implements
 * get_vblank_timestamp of struct drm_crtc_funcs for CRTCs that require a
 * VBLANK timer. It returns the timestamp according to the timer's expiry
 * time.
 *
 * See also struct &drm_crtc_funcs.get_vblank_timestamp.
 *
 * Returns:
 * True on success, or false otherwise.
 */
bool drm_crtc_vblank_helper_get_vblank_timestamp_from_timer(struct drm_crtc *crtc,
							    int *max_error,
							    ktime_t *vblank_time,
							    bool in_vblank_irq)
{
	drm_crtc_vblank_get_vblank_timeout(crtc, vblank_time);

	return true;
}
EXPORT_SYMBOL(drm_crtc_vblank_helper_get_vblank_timestamp_from_timer);
+12 −0
Original line number Diff line number Diff line
@@ -490,6 +490,18 @@ struct drm_crtc_helper_funcs {
				     bool in_vblank_irq, int *vpos, int *hpos,
				     ktime_t *stime, ktime_t *etime,
				     const struct drm_display_mode *mode);

	/**
	 * @handle_vblank_timeout: Handles timeouts of the vblank timer.
	 *
	 * Called by CRTC's the vblank timer on each timeout. Semantics is
	 * equivalient to drm_crtc_handle_vblank(). Implementations should
	 * invoke drm_crtc_handle_vblank() as part of processing the timeout.
	 *
	 * This callback is optional. If unset, the vblank timer invokes
	 * drm_crtc_handle_vblank() directly.
	 */
	bool (*handle_vblank_timeout)(struct drm_crtc *crtc);
};

/**
Loading