diff --git a/patches/0037-drm-vblank-timer-infrastructure.patch b/patches/0037-drm-vblank-timer-infrastructure.patch new file mode 100644 index 00000000..61987f24 --- /dev/null +++ b/patches/0037-drm-vblank-timer-infrastructure.patch @@ -0,0 +1,524 @@ +From 74afeb8128502a529041a2566febd26053a7be11 Mon Sep 17 00:00:00 2001 +From: Thomas Zimmermann +Date: Tue, 16 Sep 2025 10:36:19 +0200 +Subject: [PATCH] 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 3a0709928b17 ("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: Thomas Zimmermann +Tested-by: Louis Chauvet +Reviewed-by: Louis Chauvet +Reviewed-by: Javier Martinez Canillas +Tested-by: Michael Kelley +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 +--- + Documentation/gpu/drm-kms-helpers.rst | 12 ++ + drivers/gpu/drm/Makefile | 3 +- + drivers/gpu/drm/drm_vblank.c | 172 ++++++++++++++++++++++- + drivers/gpu/drm/drm_vblank_helper.c | 96 +++++++++++++ + include/drm/drm_modeset_helper_vtables.h | 12 ++ + include/drm/drm_vblank.h | 32 +++++ + include/drm/drm_vblank_helper.h | 33 +++++ + 7 files changed, 357 insertions(+), 3 deletions(-) + create mode 100644 drivers/gpu/drm/drm_vblank_helper.c + create mode 100644 include/drm/drm_vblank_helper.h + +diff --git a/Documentation/gpu/drm-kms-helpers.rst b/Documentation/gpu/drm-kms-helpers.rst +index 5139705089f200..781129f78b06b8 100644 +--- a/Documentation/gpu/drm-kms-helpers.rst ++++ b/Documentation/gpu/drm-kms-helpers.rst +@@ -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 + =========================== + +diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile +index 4a899e75a25cf..c5ee3e0f1fc6 100644 +--- a/drivers/gpu/drm/Makefile ++++ b/drivers/gpu/drm/Makefile +@@ -145,3 +145,4 @@ drm_kms_helper-y := \ + drm_self_refresh_helper.o \ +- drm_simple_kms_helper.o ++ drm_simple_kms_helper.o \ ++ drm_vblank_helper.o + drm_kms_helper-$(CONFIG_DRM_CLIENT_SETUP) += \ +diff --git a/drivers/gpu/drm/drm_vblank.c b/drivers/gpu/drm/drm_vblank.c +index 46f59883183d97..61e211fd3c9c8a 100644 +--- a/drivers/gpu/drm/drm_vblank.c ++++ b/drivers/gpu/drm/drm_vblank.c +@@ -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); +diff --git a/drivers/gpu/drm/drm_vblank_helper.c b/drivers/gpu/drm/drm_vblank_helper.c +new file mode 100644 +index 00000000000000..f94d1e706191b2 +--- /dev/null ++++ b/drivers/gpu/drm/drm_vblank_helper.c +@@ -0,0 +1,96 @@ ++// SPDX-License-Identifier: MIT ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/** ++ * 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); +diff --git a/include/drm/drm_modeset_helper_vtables.h b/include/drm/drm_modeset_helper_vtables.h +index ce7c7aeac887bb..fe32854b7ffec0 100644 +--- a/include/drm/drm_modeset_helper_vtables.h ++++ b/include/drm/drm_modeset_helper_vtables.h +@@ -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); + }; + + /** +diff --git a/include/drm/drm_vblank.h b/include/drm/drm_vblank.h +index 151ab1e85b1b76..ffa564d7963849 100644 +--- a/include/drm/drm_vblank.h ++++ b/include/drm/drm_vblank.h +@@ -25,6 +25,7 @@ + #define _DRM_VBLANK_H_ + + #include ++#include + #include + #include + #include +@@ -103,6 +104,28 @@ struct drm_vblank_crtc_config { + bool disable_immediate; + }; + ++/** ++ * struct drm_vblank_crtc_timer - vblank timer for a CRTC ++ */ ++struct drm_vblank_crtc_timer { ++ /** ++ * @timer: The vblank's high-resolution timer ++ */ ++ struct hrtimer timer; ++ /** ++ * @interval_lock: Protects @interval ++ */ ++ spinlock_t interval_lock; ++ /** ++ * @interval: Duration between two vblanks ++ */ ++ ktime_t interval; ++ /** ++ * @crtc: The timer's CRTC ++ */ ++ struct drm_crtc *crtc; ++}; ++ + /** + * struct drm_vblank_crtc - vblank tracking for a CRTC + * +@@ -254,6 +277,11 @@ struct drm_vblank_crtc { + * cancelled. + */ + wait_queue_head_t work_wait_queue; ++ ++ /** ++ * @vblank_timer: Holds the state of the vblank timer ++ */ ++ struct drm_vblank_crtc_timer vblank_timer; + }; + + struct drm_vblank_crtc *drm_crtc_vblank_crtc(struct drm_crtc *crtc); +@@ -290,6 +318,10 @@ 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); + ++int drm_crtc_vblank_start_timer(struct drm_crtc *crtc); ++void drm_crtc_vblank_cancel_timer(struct drm_crtc *crtc); ++void drm_crtc_vblank_get_vblank_timeout(struct drm_crtc *crtc, ktime_t *vblank_time); ++ + /* + * Helpers for struct drm_crtc_funcs + */ +diff --git a/include/drm/drm_vblank_helper.h b/include/drm/drm_vblank_helper.h +new file mode 100644 +index 00000000000000..74a971d0cfba0b +--- /dev/null ++++ b/include/drm/drm_vblank_helper.h +@@ -0,0 +1,33 @@ ++/* SPDX-License-Identifier: GPL-2.0+ */ ++ ++#ifndef _DRM_VBLANK_HELPER_H_ ++#define _DRM_VBLANK_HELPER_H_ ++ ++#include ++#include ++ ++struct drm_crtc; ++ ++/* ++ * VBLANK timer ++ */ ++ ++int drm_crtc_vblank_helper_enable_vblank_timer(struct drm_crtc *crtc); ++void drm_crtc_vblank_helper_disable_vblank_timer(struct drm_crtc *crtc); ++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_TIMER_FUNCS - Default implementation for VBLANK timers ++ * ++ * This macro initializes struct &drm_crtc_funcs to default helpers for ++ * VBLANK timers. ++ */ ++#define DRM_CRTC_VBLANK_TIMER_FUNCS \ ++ .enable_vblank = drm_crtc_vblank_helper_enable_vblank_timer, \ ++ .disable_vblank = drm_crtc_vblank_helper_disable_vblank_timer, \ ++ .get_vblank_timestamp = drm_crtc_vblank_helper_get_vblank_timestamp_from_timer ++ ++#endif diff --git a/patches/0038-drm-vblank-linux-6.12-compat.patch b/patches/0038-drm-vblank-linux-6.12-compat.patch new file mode 100644 index 00000000..d086e245 --- /dev/null +++ b/patches/0038-drm-vblank-linux-6.12-compat.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Matej Hrica +Subject: [PATCH] drm/vblank: Use Linux 6.12 hrtimer initialization API + +Linux 6.12 predates hrtimer_setup(). Initialize timer and callback separately. + +--- a/drivers/gpu/drm/drm_vblank.c ++++ b/drivers/gpu/drm/drm_vblank.c +@@ -2233,8 +2233,8 @@ int drm_crtc_vblank_start_timer(struct drm_crtc *crtc) + */ + vtimer->crtc = crtc; + spin_lock_init(&vtimer->interval_lock); +- hrtimer_setup(&vtimer->timer, drm_vblank_timer_function, +- CLOCK_MONOTONIC, HRTIMER_MODE_REL); ++ hrtimer_init(&vtimer->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); ++ vtimer->timer.function = drm_vblank_timer_function; + } else { + /* + * Timer should not be active. If it is, wait for the diff --git a/patches/0039-drm-virtgpu-use-vblank-timer.patch b/patches/0039-drm-virtgpu-use-vblank-timer.patch new file mode 100644 index 00000000..9a2a4a93 --- /dev/null +++ b/patches/0039-drm-virtgpu-use-vblank-timer.patch @@ -0,0 +1,90 @@ +From a036f5fceedb9fbd715565fef7b824a121503de7 Mon Sep 17 00:00:00 2001 +From: Thomas Zimmermann +Date: Wed, 8 Oct 2025 15:06:51 +0200 +Subject: [PATCH] drm/virtgpu: Use vblank timer + +Backport of upstream commit a036f5fceedb9fbd715565fef7b824a121503de7. + +--- a/drivers/gpu/drm/virtio/virtgpu_display.c ++++ b/drivers/gpu/drm/virtio/virtgpu_display.c +@@ -32,6 +32,8 @@ + #include + #include + #include ++#include ++#include + + #include "virtgpu_drv.h" + +@@ -55,6 +57,7 @@ + .reset = drm_atomic_helper_crtc_reset, + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, ++ DRM_CRTC_VBLANK_TIMER_FUNCS, + }; + + static const struct drm_framebuffer_funcs virtio_gpu_fb_funcs = { +@@ -98,6 +101,7 @@ + static void virtio_gpu_crtc_atomic_enable(struct drm_crtc *crtc, + struct drm_atomic_state *state) + { ++ drm_crtc_vblank_on(crtc); + } + + static void virtio_gpu_crtc_atomic_disable(struct drm_crtc *crtc, +@@ -107,6 +111,8 @@ + struct virtio_gpu_device *vgdev = dev->dev_private; + struct virtio_gpu_output *output = drm_crtc_to_virtio_gpu_output(crtc); + ++ drm_crtc_vblank_off(crtc); ++ + virtio_gpu_cmd_set_scanout(vgdev, output->index, 0, 0, 0, 0, 0); + virtio_gpu_notify(vgdev); + } +@@ -120,9 +126,10 @@ + static void virtio_gpu_crtc_atomic_flush(struct drm_crtc *crtc, + struct drm_atomic_state *state) + { +- struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, +- crtc); ++ struct drm_device *dev = crtc->dev; ++ struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + struct virtio_gpu_output *output = drm_crtc_to_virtio_gpu_output(crtc); ++ struct drm_pending_vblank_event *event; + + /* + * virtio-gpu can't do modeset and plane update operations +@@ -133,6 +140,20 @@ + if (drm_atomic_crtc_needs_modeset(crtc_state)) { + output->needs_modeset = true; + } ++ ++ spin_lock_irq(&dev->event_lock); ++ ++ event = crtc_state->event; ++ crtc_state->event = NULL; ++ ++ if (event) { ++ if (drm_crtc_vblank_get(crtc) == 0) ++ drm_crtc_arm_vblank_event(crtc, event); ++ else ++ drm_crtc_send_vblank_event(crtc, event); ++ } ++ ++ spin_unlock_irq(&dev->event_lock); + } + + static const struct drm_crtc_helper_funcs virtio_gpu_crtc_helper_funcs = { +@@ -355,6 +376,10 @@ + for (i = 0 ; i < vgdev->num_scanouts; ++i) + vgdev_output_init(vgdev, i); + ++ ret = drm_vblank_init(vgdev->ddev, vgdev->num_scanouts); ++ if (ret) ++ return ret; ++ + drm_mode_config_reset(vgdev->ddev); + return 0; + } +-- +2.50.1