dri-devel.lists.freedesktop.org archive mirror
 help / color / mirror / Atom feed
* [RFC PATCH 0/6] drm: Add vblank timers for devices without interrupts
@ 2025-06-05 15:24 Thomas Zimmermann
  2025-06-05 15:24 ` [PATCH 1/6] drm/vblank: Add vblank timer Thomas Zimmermann
                   ` (5 more replies)
  0 siblings, 6 replies; 10+ messages in thread
From: Thomas Zimmermann @ 2025-06-05 15:24 UTC (permalink / raw)
  To: mhklinux, maarten.lankhorst, mripard, airlied, simona,
	drawat.floss, javierm, kraxel, louis.chauvet, hamohammed.sa,
	melissa.srw, fvogt
  Cc: dri-devel, linux-hyperv, virtualization, Thomas Zimmermann

Compositors often depend on vblanks to limit their display-update
rate. Without, they see vblank events ASAP, which creates high CPU
overhead. This is especially a problem with virtual devices with fast
framebuffer access.

The series moves vkms' vblank timer to DRM and converts a number of
drivers. The final patch rate-limits the output of DRM's framebuffer
console to vblank intervals. It has been taken from [1]. It reduces
the time for doing

  time find /usr/src/linux

from 24s to 20s with the patched bochs driver. Apparently the system
is spending less CPU overhead on display updates.

This is an RFC patchset to see if the approach is feasible. It's been
motivated by a recent discussion about hypervdrm [2] and other long-
standing bug reports. [3][4]

[1] https://patchwork.freedesktop.org/series/66442/
[2] https://lore.kernel.org/dri-devel/20250523161522.409504-1-mhklinux@outlook.com/T/#ma2ebb52b60bfb0325879349377738fadcd7cb7ef
[3] https://bugzilla.suse.com/show_bug.cgi?id=1189174
[4] https://invent.kde.org/plasma/kwin/-/merge_requests/1229#note_284606

Thomas Zimmermann (6):
  drm/vblank: Add vblank timer
  drm/vkms: Use vblank timer
  drm/simpledrm: Use vblank timer
  drm/bochs: Use vblank timer
  drm/hypervdrm: Use vblank timer
  drm/fb-helper: Synchronize dirty worker with vblank

 drivers/gpu/drm/Makefile                    |   3 +-
 drivers/gpu/drm/drm_fb_helper.c             |  20 ++++
 drivers/gpu/drm/drm_vblank_timer.c          | 100 ++++++++++++++++++++
 drivers/gpu/drm/hyperv/hyperv_drm.h         |   4 +
 drivers/gpu/drm/hyperv/hyperv_drm_modeset.c |  70 ++++++++++++++
 drivers/gpu/drm/sysfb/simpledrm.c           |  81 ++++++++++++++++
 drivers/gpu/drm/tiny/bochs.c                |  68 +++++++++++++
 drivers/gpu/drm/vkms/vkms_crtc.c            |  49 +++-------
 drivers/gpu/drm/vkms/vkms_drv.h             |   6 +-
 include/drm/drm_vblank_timer.h              |  26 +++++
 10 files changed, 386 insertions(+), 41 deletions(-)
 create mode 100644 drivers/gpu/drm/drm_vblank_timer.c
 create mode 100644 include/drm/drm_vblank_timer.h

-- 
2.49.0


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH 1/6] drm/vblank: Add vblank timer
  2025-06-05 15:24 [RFC PATCH 0/6] drm: Add vblank timers for devices without interrupts Thomas Zimmermann
@ 2025-06-05 15:24 ` Thomas Zimmermann
  2025-06-06  8:43   ` Louis Chauvet
  2025-06-05 15:24 ` [PATCH 2/6] drm/vkms: Use " Thomas Zimmermann
                   ` (4 subsequent siblings)
  5 siblings, 1 reply; 10+ messages in thread
From: Thomas Zimmermann @ 2025-06-05 15:24 UTC (permalink / raw)
  To: mhklinux, maarten.lankhorst, mripard, airlied, simona,
	drawat.floss, javierm, kraxel, louis.chauvet, hamohammed.sa,
	melissa.srw, fvogt
  Cc: dri-devel, linux-hyperv, virtualization, Thomas Zimmermann

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").

Signed-off-by: Thomas Zimmermann <tzimmermann@suse.de>
---
 drivers/gpu/drm/Makefile           |   3 +-
 drivers/gpu/drm/drm_vblank_timer.c | 100 +++++++++++++++++++++++++++++
 include/drm/drm_vblank_timer.h     |  26 ++++++++
 3 files changed, 128 insertions(+), 1 deletion(-)
 create mode 100644 drivers/gpu/drm/drm_vblank_timer.c
 create mode 100644 include/drm/drm_vblank_timer.h

diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index b5d5561bbe5f..6722e2d1aa7e 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -146,7 +146,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_timer.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
diff --git a/drivers/gpu/drm/drm_vblank_timer.c b/drivers/gpu/drm/drm_vblank_timer.c
new file mode 100644
index 000000000000..be46d3135c8e
--- /dev/null
+++ b/drivers/gpu/drm/drm_vblank_timer.c
@@ -0,0 +1,100 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#include <linux/hrtimer.h>
+
+#include <drm/drm_crtc.h>
+#include <drm/drm_managed.h>
+#include <drm/drm_print.h>
+#include <drm/drm_vblank.h>
+#include <drm/drm_vblank_timer.h>
+
+static enum hrtimer_restart drm_vblank_timer_function(struct hrtimer *timer)
+{
+	struct drm_vblank_timer *vtimer = container_of(timer, struct drm_vblank_timer, timer);
+	struct drm_crtc *crtc = vtimer->crtc;
+	struct drm_device *dev = crtc->dev;
+	u64 ret_overrun;
+	bool succ;
+
+	ret_overrun = hrtimer_forward_now(&vtimer->timer, vtimer->period_ns);
+	if (ret_overrun != 1)
+		drm_warn(dev, "vblank timer overrun\n");
+
+	if (vtimer->crtc_handle_vblank)
+		succ = vtimer->crtc_handle_vblank(crtc);
+	else
+		succ = drm_crtc_handle_vblank(crtc);
+	if (!succ)
+		return HRTIMER_NORESTART;
+
+	return HRTIMER_RESTART;
+}
+
+static void drmm_vblank_timer_release(struct drm_device *dev, void *res)
+{
+	struct drm_vblank_timer *vtimer = res;
+
+	hrtimer_cancel(&vtimer->timer);
+}
+
+int drmm_vblank_timer_init(struct drm_vblank_timer *vtimer, struct drm_crtc *crtc,
+			   bool (*crtc_handle_vblank)(struct drm_crtc *crtc))
+{
+	struct hrtimer *timer = &vtimer->timer;
+
+	vtimer->crtc = crtc;
+	vtimer->crtc_handle_vblank = crtc_handle_vblank;
+
+	hrtimer_setup(timer, drm_vblank_timer_function, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+
+	return drmm_add_action_or_reset(crtc->dev, drmm_vblank_timer_release, vtimer);
+}
+EXPORT_SYMBOL(drmm_vblank_timer_init);
+
+void drm_vblank_timer_start(struct drm_vblank_timer *vtimer)
+{
+	struct drm_crtc *crtc = vtimer->crtc;
+	struct drm_vblank_crtc *vblank = drm_crtc_vblank_crtc(crtc);
+
+	drm_calc_timestamping_constants(crtc, &crtc->mode);
+
+	vtimer->period_ns = ktime_set(0, vblank->framedur_ns);
+	hrtimer_start(&vtimer->timer, vtimer->period_ns, HRTIMER_MODE_REL);
+}
+EXPORT_SYMBOL(drm_vblank_timer_start);
+
+void drm_vblank_timer_cancel(struct drm_vblank_timer *vtimer)
+{
+	hrtimer_cancel(&vtimer->timer);
+}
+EXPORT_SYMBOL(drm_vblank_timer_cancel);
+
+bool drm_vblank_timer_get_vblank_timestamp(struct drm_vblank_timer *vtimer,
+					   int *max_error, ktime_t *vblank_time,
+					   bool in_vblank_irq)
+{
+	struct drm_crtc *crtc = vtimer->crtc;
+	struct drm_vblank_crtc *vblank = drm_crtc_vblank_crtc(crtc);
+
+	if (!READ_ONCE(vblank->enabled)) {
+		*vblank_time = ktime_get();
+		return true;
+	}
+
+	*vblank_time = READ_ONCE(vtimer->timer.node.expires);
+
+	if (WARN_ON(*vblank_time == vblank->time))
+		return true;
+
+	/*
+	 * 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
+	 * timestampe by one frame.
+	 */
+	*vblank_time -= vtimer->period_ns;
+
+	return true;
+}
+EXPORT_SYMBOL(drm_vblank_timer_get_vblank_timestamp);
diff --git a/include/drm/drm_vblank_timer.h b/include/drm/drm_vblank_timer.h
new file mode 100644
index 000000000000..0b827ff1f59c
--- /dev/null
+++ b/include/drm/drm_vblank_timer.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+
+#ifndef _DRM_VBLANK_TIMER_H_
+#define _DRM_VBLANK_TIMER_H_
+
+#include <linux/hrtimer_types.h>
+#include <linux/types.h>
+
+struct drm_crtc;
+
+struct drm_vblank_timer {
+	struct drm_crtc *crtc;
+	bool (*crtc_handle_vblank)(struct drm_crtc *crtc);
+	ktime_t period_ns;
+	struct hrtimer timer;
+};
+
+int drmm_vblank_timer_init(struct drm_vblank_timer *vtimer, struct drm_crtc *crtc,
+			   bool (*handle_vblank)(struct drm_crtc *crtc));
+void drm_vblank_timer_start(struct drm_vblank_timer *vtimer);
+void drm_vblank_timer_cancel(struct drm_vblank_timer *vtimer);
+bool drm_vblank_timer_get_vblank_timestamp(struct drm_vblank_timer *vtimer,
+					   int *max_error, ktime_t *vblank_time,
+					   bool in_vblank_irq);
+
+#endif
-- 
2.49.0


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 2/6] drm/vkms: Use vblank timer
  2025-06-05 15:24 [RFC PATCH 0/6] drm: Add vblank timers for devices without interrupts Thomas Zimmermann
  2025-06-05 15:24 ` [PATCH 1/6] drm/vblank: Add vblank timer Thomas Zimmermann
@ 2025-06-05 15:24 ` Thomas Zimmermann
  2025-06-06  9:14   ` Louis Chauvet
  2025-06-05 15:24 ` [PATCH 3/6] drm/simpledrm: " Thomas Zimmermann
                   ` (3 subsequent siblings)
  5 siblings, 1 reply; 10+ messages in thread
From: Thomas Zimmermann @ 2025-06-05 15:24 UTC (permalink / raw)
  To: mhklinux, maarten.lankhorst, mripard, airlied, simona,
	drawat.floss, javierm, kraxel, louis.chauvet, hamohammed.sa,
	melissa.srw, fvogt
  Cc: dri-devel, linux-hyperv, virtualization, Thomas Zimmermann

Replace vkms' vblank timer with the DRM implementation. The DRM
code is mostly identical.

Signed-off-by: Thomas Zimmermann <tzimmermann@suse.de>
---
 drivers/gpu/drm/vkms/vkms_crtc.c | 49 +++++++-------------------------
 drivers/gpu/drm/vkms/vkms_drv.h  |  6 ++--
 2 files changed, 15 insertions(+), 40 deletions(-)

diff --git a/drivers/gpu/drm/vkms/vkms_crtc.c b/drivers/gpu/drm/vkms/vkms_crtc.c
index 8c9898b9055d..5b7829e8c900 100644
--- a/drivers/gpu/drm/vkms/vkms_crtc.c
+++ b/drivers/gpu/drm/vkms/vkms_crtc.c
@@ -10,22 +10,14 @@
 
 #include "vkms_drv.h"
 
-static enum hrtimer_restart vkms_vblank_simulate(struct hrtimer *timer)
+static bool vkms_crtc_handle_vblank(struct drm_crtc *crtc)
 {
-	struct vkms_output *output = container_of(timer, struct vkms_output,
-						  vblank_hrtimer);
-	struct drm_crtc *crtc = &output->crtc;
+	struct vkms_output *output = drm_crtc_to_vkms_output(crtc);
 	struct vkms_crtc_state *state;
-	u64 ret_overrun;
 	bool ret, fence_cookie;
 
 	fence_cookie = dma_fence_begin_signalling();
 
-	ret_overrun = hrtimer_forward_now(&output->vblank_hrtimer,
-					  output->period_ns);
-	if (ret_overrun != 1)
-		pr_warn("%s: vblank timer overrun\n", __func__);
-
 	spin_lock(&output->lock);
 	ret = drm_crtc_handle_vblank(crtc);
 	if (!ret)
@@ -57,18 +49,14 @@ static enum hrtimer_restart vkms_vblank_simulate(struct hrtimer *timer)
 
 	dma_fence_end_signalling(fence_cookie);
 
-	return HRTIMER_RESTART;
+	return true;
 }
 
 static int vkms_enable_vblank(struct drm_crtc *crtc)
 {
-	struct drm_vblank_crtc *vblank = drm_crtc_vblank_crtc(crtc);
 	struct vkms_output *out = drm_crtc_to_vkms_output(crtc);
 
-	hrtimer_setup(&out->vblank_hrtimer, &vkms_vblank_simulate, CLOCK_MONOTONIC,
-		      HRTIMER_MODE_REL);
-	out->period_ns = ktime_set(0, vblank->framedur_ns);
-	hrtimer_start(&out->vblank_hrtimer, out->period_ns, HRTIMER_MODE_REL);
+	drm_vblank_timer_start(&out->vtimer);
 
 	return 0;
 }
@@ -77,7 +65,7 @@ static void vkms_disable_vblank(struct drm_crtc *crtc)
 {
 	struct vkms_output *out = drm_crtc_to_vkms_output(crtc);
 
-	hrtimer_cancel(&out->vblank_hrtimer);
+	drm_vblank_timer_cancel(&out->vtimer);
 }
 
 static bool vkms_get_vblank_timestamp(struct drm_crtc *crtc,
@@ -85,28 +73,9 @@ static bool vkms_get_vblank_timestamp(struct drm_crtc *crtc,
 				      bool in_vblank_irq)
 {
 	struct vkms_output *output = drm_crtc_to_vkms_output(crtc);
-	struct drm_vblank_crtc *vblank = drm_crtc_vblank_crtc(crtc);
-
-	if (!READ_ONCE(vblank->enabled)) {
-		*vblank_time = ktime_get();
-		return true;
-	}
-
-	*vblank_time = READ_ONCE(output->vblank_hrtimer.node.expires);
 
-	if (WARN_ON(*vblank_time == vblank->time))
-		return true;
-
-	/*
-	 * 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
-	 * timestampe by one frame.
-	 */
-	*vblank_time -= output->period_ns;
-
-	return true;
+	return drm_vblank_timer_get_vblank_timestamp(&output->vtimer, max_error,
+						     vblank_time, in_vblank_irq);
 }
 
 static struct drm_crtc_state *
@@ -274,6 +243,7 @@ struct vkms_output *vkms_crtc_init(struct drm_device *dev, struct drm_plane *pri
 {
 	struct vkms_output *vkms_out;
 	struct drm_crtc *crtc;
+	struct drm_vblank_timer *vtimer;
 	int ret;
 
 	vkms_out = drmm_crtc_alloc_with_planes(dev, struct vkms_output, crtc,
@@ -285,6 +255,7 @@ struct vkms_output *vkms_crtc_init(struct drm_device *dev, struct drm_plane *pri
 	}
 
 	crtc = &vkms_out->crtc;
+	vtimer = &vkms_out->vtimer;
 
 	drm_crtc_helper_add(crtc, &vkms_crtc_helper_funcs);
 
@@ -305,5 +276,7 @@ struct vkms_output *vkms_crtc_init(struct drm_device *dev, struct drm_plane *pri
 	if (!vkms_out->composer_workq)
 		return ERR_PTR(-ENOMEM);
 
+	drmm_vblank_timer_init(vtimer, crtc, vkms_crtc_handle_vblank);
+
 	return vkms_out;
 }
diff --git a/drivers/gpu/drm/vkms/vkms_drv.h b/drivers/gpu/drm/vkms/vkms_drv.h
index a74a7fc3a056..126016898285 100644
--- a/drivers/gpu/drm/vkms/vkms_drv.h
+++ b/drivers/gpu/drm/vkms/vkms_drv.h
@@ -10,6 +10,7 @@
 #include <drm/drm_gem.h>
 #include <drm/drm_gem_atomic_helper.h>
 #include <drm/drm_encoder.h>
+#include <drm/drm_vblank_timer.h>
 #include <drm/drm_writeback.h>
 
 #define DEFAULT_DEVICE_NAME "vkms"
@@ -180,8 +181,9 @@ struct vkms_output {
 	struct drm_crtc crtc;
 	struct drm_writeback_connector wb_connector;
 	struct drm_encoder wb_encoder;
-	struct hrtimer vblank_hrtimer;
-	ktime_t period_ns;
+	struct drm_vblank_timer vtimer;
+	struct drm_pending_vblank_event *event;
+	/* ordered wq for composer_work */
 	struct workqueue_struct *composer_workq;
 	spinlock_t lock;
 
-- 
2.49.0


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 3/6] drm/simpledrm: Use vblank timer
  2025-06-05 15:24 [RFC PATCH 0/6] drm: Add vblank timers for devices without interrupts Thomas Zimmermann
  2025-06-05 15:24 ` [PATCH 1/6] drm/vblank: Add vblank timer Thomas Zimmermann
  2025-06-05 15:24 ` [PATCH 2/6] drm/vkms: Use " Thomas Zimmermann
@ 2025-06-05 15:24 ` Thomas Zimmermann
  2025-06-05 15:24 ` [PATCH 4/6] drm/bochs: " Thomas Zimmermann
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 10+ messages in thread
From: Thomas Zimmermann @ 2025-06-05 15:24 UTC (permalink / raw)
  To: mhklinux, maarten.lankhorst, mripard, airlied, simona,
	drawat.floss, javierm, kraxel, louis.chauvet, hamohammed.sa,
	melissa.srw, fvogt
  Cc: dri-devel, linux-hyperv, virtualization, Thomas Zimmermann

Simpledrm hardware does not provide vblank interrupts. Use a vblank
timer to simulate the inerrup tin software. Rate-limits the display's
update frequency to the display-mode settings.

Signed-off-by: Thomas Zimmermann <tzimmermann@suse.de>
---
 drivers/gpu/drm/sysfb/simpledrm.c | 81 +++++++++++++++++++++++++++++++
 1 file changed, 81 insertions(+)

diff --git a/drivers/gpu/drm/sysfb/simpledrm.c b/drivers/gpu/drm/sysfb/simpledrm.c
index a1c3119330de..b9766129f564 100644
--- a/drivers/gpu/drm/sysfb/simpledrm.c
+++ b/drivers/gpu/drm/sysfb/simpledrm.c
@@ -26,6 +26,8 @@
 #include <drm/drm_managed.h>
 #include <drm/drm_modeset_helper_vtables.h>
 #include <drm/drm_probe_helper.h>
+#include <drm/drm_vblank.h>
+#include <drm/drm_vblank_timer.h>
 
 #include "drm_sysfb_helper.h"
 
@@ -229,11 +231,17 @@ struct simpledrm_device {
 	/* modesetting */
 	u32 formats[DRM_SYSFB_PLANE_NFORMATS(1)];
 	struct drm_plane primary_plane;
+	struct drm_vblank_timer vtimer;
 	struct drm_crtc crtc;
 	struct drm_encoder encoder;
 	struct drm_connector connector;
 };
 
+static struct simpledrm_device *to_simpledrm_device(struct drm_device *dev)
+{
+	return container_of(to_drm_sysfb_device(dev), struct simpledrm_device, sysfb);
+}
+
 /*
  * Hardware
  */
@@ -564,13 +572,79 @@ static const struct drm_plane_funcs simpledrm_primary_plane_funcs = {
 	.destroy = drm_plane_cleanup,
 };
 
+static void simpledrm_crtc_helper_atomic_flush(struct drm_crtc *crtc,
+					       struct drm_atomic_state *state)
+{
+	struct drm_device *dev = crtc->dev;
+	struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
+	struct drm_pending_vblank_event *event;
+
+	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 void simpledrm_crtc_helper_atomic_enable(struct drm_crtc *crtc,
+						struct drm_atomic_state *state)
+{
+	drm_crtc_vblank_on(crtc);
+}
+
+static void simpledrm_crtc_helper_atomic_disable(struct drm_crtc *crtc,
+						 struct drm_atomic_state *state)
+{
+	drm_crtc_vblank_off(crtc);
+}
+
 static const struct drm_crtc_helper_funcs simpledrm_crtc_helper_funcs = {
 	DRM_SYSFB_CRTC_HELPER_FUNCS,
+	.atomic_flush = simpledrm_crtc_helper_atomic_flush,
+	.atomic_enable = simpledrm_crtc_helper_atomic_enable,
+	.atomic_disable = simpledrm_crtc_helper_atomic_disable,
 };
 
+static int simpledrm_crtc_enable_vblank(struct drm_crtc *crtc)
+{
+	struct simpledrm_device *sdev = to_simpledrm_device(crtc->dev);
+
+	drm_vblank_timer_start(&sdev->vtimer);
+
+	return 0;
+}
+
+static void simpledrm_crtc_disable_vblank(struct drm_crtc *crtc)
+{
+	struct simpledrm_device *sdev = to_simpledrm_device(crtc->dev);
+
+	drm_vblank_timer_cancel(&sdev->vtimer);
+}
+
+static bool simpledrm_crtc_get_vblank_timestamp(struct drm_crtc *crtc,
+						int *max_error, ktime_t *vblank_time,
+						bool in_vblank_irq)
+{
+	struct simpledrm_device *sdev = to_simpledrm_device(crtc->dev);
+
+	return drm_vblank_timer_get_vblank_timestamp(&sdev->vtimer, max_error,
+						     vblank_time, in_vblank_irq);
+}
+
 static const struct drm_crtc_funcs simpledrm_crtc_funcs = {
 	DRM_SYSFB_CRTC_FUNCS,
 	.destroy = drm_crtc_cleanup,
+	.enable_vblank = simpledrm_crtc_enable_vblank,
+	.disable_vblank = simpledrm_crtc_disable_vblank,
+	.get_vblank_timestamp = simpledrm_crtc_get_vblank_timestamp,
 };
 
 static const struct drm_encoder_funcs simpledrm_encoder_funcs = {
@@ -611,6 +685,7 @@ static struct simpledrm_device *simpledrm_device_create(struct drm_driver *drv,
 	struct drm_crtc *crtc;
 	struct drm_encoder *encoder;
 	struct drm_connector *connector;
+	struct drm_vblank_timer *vtimer;
 	unsigned long max_width, max_height;
 	size_t nformats;
 	int ret;
@@ -812,6 +887,12 @@ static struct simpledrm_device *simpledrm_device_create(struct drm_driver *drv,
 	if (ret)
 		return ERR_PTR(ret);
 
+	/* Vertical blanking */
+
+	vtimer = &sdev->vtimer;
+	drmm_vblank_timer_init(vtimer, crtc, NULL);
+	drm_vblank_init(dev, 1);
+
 	drm_mode_config_reset(dev);
 
 	return sdev;
-- 
2.49.0


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 4/6] drm/bochs: Use vblank timer
  2025-06-05 15:24 [RFC PATCH 0/6] drm: Add vblank timers for devices without interrupts Thomas Zimmermann
                   ` (2 preceding siblings ...)
  2025-06-05 15:24 ` [PATCH 3/6] drm/simpledrm: " Thomas Zimmermann
@ 2025-06-05 15:24 ` Thomas Zimmermann
  2025-06-05 15:24 ` [PATCH 5/6] drm/hypervdrm: " Thomas Zimmermann
  2025-06-05 15:24 ` [PATCH 6/6] drm/fb-helper: Synchronize dirty worker with vblank Thomas Zimmermann
  5 siblings, 0 replies; 10+ messages in thread
From: Thomas Zimmermann @ 2025-06-05 15:24 UTC (permalink / raw)
  To: mhklinux, maarten.lankhorst, mripard, airlied, simona,
	drawat.floss, javierm, kraxel, louis.chauvet, hamohammed.sa,
	melissa.srw, fvogt
  Cc: dri-devel, linux-hyperv, virtualization, Thomas Zimmermann

Bochs' virtual hardware does not provide vblank interrupts. Use a
vblank timer to simulate the interrupt in software. Rate-limits the
display's update frequency to the display-mode settings. Avoids
excessive CPU overhead with compositors that do not rate-limit their
output.

Signed-off-by: Thomas Zimmermann <tzimmermann@suse.de>
---
 drivers/gpu/drm/tiny/bochs.c | 68 ++++++++++++++++++++++++++++++++++++
 1 file changed, 68 insertions(+)

diff --git a/drivers/gpu/drm/tiny/bochs.c b/drivers/gpu/drm/tiny/bochs.c
index 8706763af8fb..dbc6a15c1a58 100644
--- a/drivers/gpu/drm/tiny/bochs.c
+++ b/drivers/gpu/drm/tiny/bochs.c
@@ -21,6 +21,8 @@
 #include <drm/drm_module.h>
 #include <drm/drm_plane_helper.h>
 #include <drm/drm_probe_helper.h>
+#include <drm/drm_vblank.h>
+#include <drm/drm_vblank_timer.h>
 
 #include <video/vga.h>
 
@@ -95,6 +97,7 @@ struct bochs_device {
 
 	/* drm */
 	struct drm_plane primary_plane;
+	struct drm_vblank_timer vtimer;
 	struct drm_crtc crtc;
 	struct drm_encoder encoder;
 	struct drm_connector connector;
@@ -501,12 +504,35 @@ static int bochs_crtc_helper_atomic_check(struct drm_crtc *crtc,
 	return drm_atomic_helper_check_crtc_primary_plane(crtc_state);
 }
 
+static void bochs_crtc_helper_atomic_flush(struct drm_crtc *crtc,
+					   struct drm_atomic_state *state)
+{
+	struct drm_device *dev = crtc->dev;
+	struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
+	struct drm_pending_vblank_event *event;
+
+	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 void bochs_crtc_helper_atomic_enable(struct drm_crtc *crtc,
 					    struct drm_atomic_state *state)
 {
 	struct bochs_device *bochs = to_bochs_device(crtc->dev);
 
 	bochs_hw_blank(bochs, false);
+	drm_crtc_vblank_on(crtc);
 }
 
 static void bochs_crtc_helper_atomic_disable(struct drm_crtc *crtc,
@@ -514,16 +540,44 @@ static void bochs_crtc_helper_atomic_disable(struct drm_crtc *crtc,
 {
 	struct bochs_device *bochs = to_bochs_device(crtc->dev);
 
+	drm_crtc_vblank_off(crtc);
 	bochs_hw_blank(bochs, true);
 }
 
 static const struct drm_crtc_helper_funcs bochs_crtc_helper_funcs = {
 	.mode_set_nofb = bochs_crtc_helper_mode_set_nofb,
 	.atomic_check = bochs_crtc_helper_atomic_check,
+	.atomic_flush = bochs_crtc_helper_atomic_flush,
 	.atomic_enable = bochs_crtc_helper_atomic_enable,
 	.atomic_disable = bochs_crtc_helper_atomic_disable,
 };
 
+static int bochs_crtc_enable_vblank(struct drm_crtc *crtc)
+{
+	struct bochs_device *bochs = to_bochs_device(crtc->dev);
+
+	drm_vblank_timer_start(&bochs->vtimer);
+
+	return 0;
+}
+
+static void bochs_crtc_disable_vblank(struct drm_crtc *crtc)
+{
+	struct bochs_device *bochs = to_bochs_device(crtc->dev);
+
+	drm_vblank_timer_cancel(&bochs->vtimer);
+}
+
+static bool bochs_crtc_get_vblank_timestamp(struct drm_crtc *crtc,
+					    int *max_error, ktime_t *vblank_time,
+					    bool in_vblank_irq)
+{
+	struct bochs_device *bochs = to_bochs_device(crtc->dev);
+
+	return drm_vblank_timer_get_vblank_timestamp(&bochs->vtimer, max_error,
+						     vblank_time, in_vblank_irq);
+}
+
 static const struct drm_crtc_funcs bochs_crtc_funcs = {
 	.reset = drm_atomic_helper_crtc_reset,
 	.destroy = drm_crtc_cleanup,
@@ -531,6 +585,9 @@ static const struct drm_crtc_funcs bochs_crtc_funcs = {
 	.page_flip = drm_atomic_helper_page_flip,
 	.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
 	.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
+	.enable_vblank = bochs_crtc_enable_vblank,
+	.disable_vblank = bochs_crtc_disable_vblank,
+	.get_vblank_timestamp = bochs_crtc_get_vblank_timestamp,
 };
 
 static const struct drm_encoder_funcs bochs_encoder_funcs = {
@@ -602,6 +659,7 @@ static int bochs_kms_init(struct bochs_device *bochs)
 	struct drm_crtc *crtc;
 	struct drm_connector *connector;
 	struct drm_encoder *encoder;
+	struct drm_vblank_timer *vtimer;
 	int ret;
 
 	ret = drmm_mode_config_init(dev);
@@ -651,6 +709,16 @@ static int bochs_kms_init(struct bochs_device *bochs)
 	drm_connector_attach_edid_property(connector);
 	drm_connector_attach_encoder(connector, encoder);
 
+	/* Vertical blanking */
+
+	ret = drm_vblank_init(dev, 1);
+	if (ret)
+		return ret;
+	vtimer = &bochs->vtimer;
+	ret = drmm_vblank_timer_init(vtimer, crtc, NULL);
+	if (ret)
+		return ret;
+
 	drm_mode_config_reset(dev);
 
 	return 0;
-- 
2.49.0


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 5/6] drm/hypervdrm: Use vblank timer
  2025-06-05 15:24 [RFC PATCH 0/6] drm: Add vblank timers for devices without interrupts Thomas Zimmermann
                   ` (3 preceding siblings ...)
  2025-06-05 15:24 ` [PATCH 4/6] drm/bochs: " Thomas Zimmermann
@ 2025-06-05 15:24 ` Thomas Zimmermann
  2025-06-05 15:24 ` [PATCH 6/6] drm/fb-helper: Synchronize dirty worker with vblank Thomas Zimmermann
  5 siblings, 0 replies; 10+ messages in thread
From: Thomas Zimmermann @ 2025-06-05 15:24 UTC (permalink / raw)
  To: mhklinux, maarten.lankhorst, mripard, airlied, simona,
	drawat.floss, javierm, kraxel, louis.chauvet, hamohammed.sa,
	melissa.srw, fvogt
  Cc: dri-devel, linux-hyperv, virtualization, Thomas Zimmermann

HyperV's virtual hardware does not provide vblank interrupts. Use a
vblank timer to simulate the interrupt in software. Rate-limits the
display's update frequency to the display-mode settings. Avoids
excessive CPU overhead with compositors that do not rate-limit their
output.

Signed-off-by: Thomas Zimmermann <tzimmermann@suse.de>
---
 drivers/gpu/drm/hyperv/hyperv_drm.h         |  4 ++
 drivers/gpu/drm/hyperv/hyperv_drm_modeset.c | 70 +++++++++++++++++++++
 2 files changed, 74 insertions(+)

diff --git a/drivers/gpu/drm/hyperv/hyperv_drm.h b/drivers/gpu/drm/hyperv/hyperv_drm.h
index 9e776112c03e..730e4a37545d 100644
--- a/drivers/gpu/drm/hyperv/hyperv_drm.h
+++ b/drivers/gpu/drm/hyperv/hyperv_drm.h
@@ -6,12 +6,16 @@
 #ifndef _HYPERV_DRM_H_
 #define _HYPERV_DRM_H_
 
+#include <drm/drm_vblank.h>
+#include <drm/drm_vblank_timer.h>
+
 #define VMBUS_MAX_PACKET_SIZE 0x4000
 
 struct hyperv_drm_device {
 	/* drm */
 	struct drm_device dev;
 	struct drm_plane plane;
+	struct drm_vblank_timer vtimer;
 	struct drm_crtc crtc;
 	struct drm_encoder encoder;
 	struct drm_connector connector;
diff --git a/drivers/gpu/drm/hyperv/hyperv_drm_modeset.c b/drivers/gpu/drm/hyperv/hyperv_drm_modeset.c
index f7d2e973f79e..ed19e471b96f 100644
--- a/drivers/gpu/drm/hyperv/hyperv_drm_modeset.c
+++ b/drivers/gpu/drm/hyperv/hyperv_drm_modeset.c
@@ -97,6 +97,28 @@ static const uint64_t hyperv_modifiers[] = {
 	DRM_FORMAT_MOD_INVALID
 };
 
+static void hyperv_crtc_helper_atomic_flush(struct drm_crtc *crtc,
+					    struct drm_atomic_state *state)
+{
+	struct drm_device *dev = crtc->dev;
+	struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
+	struct drm_pending_vblank_event *event;
+
+	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 void hyperv_crtc_helper_atomic_enable(struct drm_crtc *crtc,
 					     struct drm_atomic_state *state)
 {
@@ -110,13 +132,49 @@ static void hyperv_crtc_helper_atomic_enable(struct drm_crtc *crtc,
 				crtc_state->mode.hdisplay,
 				crtc_state->mode.vdisplay,
 				plane_state->fb->pitches[0]);
+
+	drm_crtc_vblank_on(crtc);
+}
+
+static void hyperv_crtc_helper_atomic_disable(struct drm_crtc *crtc,
+					      struct drm_atomic_state *crtc_state)
+{
+	drm_crtc_vblank_off(crtc);
 }
 
 static const struct drm_crtc_helper_funcs hyperv_crtc_helper_funcs = {
 	.atomic_check = drm_crtc_helper_atomic_check,
+	.atomic_flush = hyperv_crtc_helper_atomic_flush,
 	.atomic_enable = hyperv_crtc_helper_atomic_enable,
+	.atomic_disable = hyperv_crtc_helper_atomic_disable,
 };
 
+static int hyperv_crtc_enable_vblank(struct drm_crtc *crtc)
+{
+	struct hyperv_drm_device *hv = to_hv(crtc->dev);
+
+	drm_vblank_timer_start(&hv->vtimer);
+
+	return 0;
+}
+
+static void hyperv_crtc_disable_vblank(struct drm_crtc *crtc)
+{
+	struct hyperv_drm_device *hv = to_hv(crtc->dev);
+
+	drm_vblank_timer_cancel(&hv->vtimer);
+}
+
+static bool hyperv_crtc_get_vblank_timestamp(struct drm_crtc *crtc,
+					     int *max_error, ktime_t *vblank_time,
+					     bool in_vblank_irq)
+{
+	struct hyperv_drm_device *hv = to_hv(crtc->dev);
+
+	return drm_vblank_timer_get_vblank_timestamp(&hv->vtimer, max_error,
+						     vblank_time, in_vblank_irq);
+}
+
 static const struct drm_crtc_funcs hyperv_crtc_funcs = {
 	.reset = drm_atomic_helper_crtc_reset,
 	.destroy = drm_crtc_cleanup,
@@ -124,6 +182,9 @@ static const struct drm_crtc_funcs hyperv_crtc_funcs = {
 	.page_flip = drm_atomic_helper_page_flip,
 	.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
 	.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
+	.enable_vblank = hyperv_crtc_enable_vblank,
+	.disable_vblank = hyperv_crtc_disable_vblank,
+	.get_vblank_timestamp = hyperv_crtc_get_vblank_timestamp,
 };
 
 static int hyperv_plane_atomic_check(struct drm_plane *plane,
@@ -285,6 +346,15 @@ int hyperv_mode_config_init(struct hyperv_drm_device *hv)
 		return ret;
 	}
 
+	/* Vertical blanking */
+
+	ret = drm_vblank_init(dev, 1);
+	if (ret)
+		return ret;
+	ret = drmm_vblank_timer_init(&hv->vtimer, &hv->crtc, NULL);
+	if (ret)
+		return ret;
+
 	drm_mode_config_reset(dev);
 
 	return 0;
-- 
2.49.0


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 6/6] drm/fb-helper: Synchronize dirty worker with vblank
  2025-06-05 15:24 [RFC PATCH 0/6] drm: Add vblank timers for devices without interrupts Thomas Zimmermann
                   ` (4 preceding siblings ...)
  2025-06-05 15:24 ` [PATCH 5/6] drm/hypervdrm: " Thomas Zimmermann
@ 2025-06-05 15:24 ` Thomas Zimmermann
  5 siblings, 0 replies; 10+ messages in thread
From: Thomas Zimmermann @ 2025-06-05 15:24 UTC (permalink / raw)
  To: mhklinux, maarten.lankhorst, mripard, airlied, simona,
	drawat.floss, javierm, kraxel, louis.chauvet, hamohammed.sa,
	melissa.srw, fvogt
  Cc: dri-devel, linux-hyperv, virtualization, Thomas Zimmermann

Before updating the display from the console's shadow buffer, the dirty
worker now waits for a vblank. This allows several screen updates to pile
up and acts as a rate limiter. If a DRM master is present, it could
interfere with the vblank. Don't wait in this case.

v3:
	* add back helper->lock
	* acquire DRM master status while waiting for vblank
v2:
	* don't hold helper->lock while waiting for vblank

Signed-off-by: Thomas Zimmermann <tzimmermann@suse.de>
---
 drivers/gpu/drm/drm_fb_helper.c | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c
index 937c3939e502..e0b780634a84 100644
--- a/drivers/gpu/drm/drm_fb_helper.c
+++ b/drivers/gpu/drm/drm_fb_helper.c
@@ -364,9 +364,29 @@ static void drm_fb_helper_fb_dirty(struct drm_fb_helper *helper)
 	struct drm_device *dev = helper->dev;
 	struct drm_clip_rect *clip = &helper->damage_clip;
 	struct drm_clip_rect clip_copy;
+	struct drm_crtc *crtc;
 	unsigned long flags;
 	int ret;
 
+	/*
+	 * Rate-limit update frequency to vblank. If there's a DRM master
+	 * present, it could interfere while we're waiting for the vblank
+	 * event. Don't wait in this case.
+	 */
+	mutex_lock(&helper->lock);
+	if (!drm_master_internal_acquire(helper->dev)) {
+		goto unlock;
+	}
+	crtc = helper->client.modesets[0].crtc;
+	ret = drm_crtc_vblank_get(crtc);
+	if (!ret) {
+		drm_crtc_wait_one_vblank(crtc);
+		drm_crtc_vblank_put(crtc);
+	}
+	drm_master_internal_release(helper->dev);
+unlock:
+	mutex_unlock(&helper->lock);
+
 	if (drm_WARN_ON_ONCE(dev, !helper->funcs->fb_dirty))
 		return;
 
-- 
2.49.0


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* Re: [PATCH 1/6] drm/vblank: Add vblank timer
  2025-06-05 15:24 ` [PATCH 1/6] drm/vblank: Add vblank timer Thomas Zimmermann
@ 2025-06-06  8:43   ` Louis Chauvet
  2025-06-10 15:36     ` Thomas Zimmermann
  0 siblings, 1 reply; 10+ messages in thread
From: Louis Chauvet @ 2025-06-06  8:43 UTC (permalink / raw)
  To: Thomas Zimmermann, mhklinux, maarten.lankhorst, mripard, airlied,
	simona, drawat.floss, javierm, kraxel, hamohammed.sa, melissa.srw,
	fvogt
  Cc: dri-devel, linux-hyperv, virtualization



Le 05/06/2025 à 17:24, Thomas Zimmermann a écrit :
> 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").
> 
> Signed-off-by: Thomas Zimmermann <tzimmermann@suse.de>

Tested-by: Louis Chauvet <louis.chauvet@bootlin.com>
Reviewed-by: Louis Chauvet <louis.chauvet@bootlin.com>

> ---
>   drivers/gpu/drm/Makefile           |   3 +-
>   drivers/gpu/drm/drm_vblank_timer.c | 100 +++++++++++++++++++++++++++++
>   include/drm/drm_vblank_timer.h     |  26 ++++++++
>   3 files changed, 128 insertions(+), 1 deletion(-)
>   create mode 100644 drivers/gpu/drm/drm_vblank_timer.c
>   create mode 100644 include/drm/drm_vblank_timer.h
> 
> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> index b5d5561bbe5f..6722e2d1aa7e 100644
> --- a/drivers/gpu/drm/Makefile
> +++ b/drivers/gpu/drm/Makefile
> @@ -146,7 +146,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_timer.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
> diff --git a/drivers/gpu/drm/drm_vblank_timer.c b/drivers/gpu/drm/drm_vblank_timer.c
> new file mode 100644
> index 000000000000..be46d3135c8e
> --- /dev/null
> +++ b/drivers/gpu/drm/drm_vblank_timer.c
> @@ -0,0 +1,100 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +
> +#include <linux/hrtimer.h>
> +
> +#include <drm/drm_crtc.h>
> +#include <drm/drm_managed.h>
> +#include <drm/drm_print.h>
> +#include <drm/drm_vblank.h>
> +#include <drm/drm_vblank_timer.h>
> +
> +static enum hrtimer_restart drm_vblank_timer_function(struct hrtimer *timer)
> +{
> +	struct drm_vblank_timer *vtimer = container_of(timer, struct drm_vblank_timer, timer);
> +	struct drm_crtc *crtc = vtimer->crtc;
> +	struct drm_device *dev = crtc->dev;
> +	u64 ret_overrun;
> +	bool succ;
> +
> +	ret_overrun = hrtimer_forward_now(&vtimer->timer, vtimer->period_ns);
> +	if (ret_overrun != 1)
> +		drm_warn(dev, "vblank timer overrun\n");
> +
> +	if (vtimer->crtc_handle_vblank)
> +		succ = vtimer->crtc_handle_vblank(crtc);
> +	else
> +		succ = drm_crtc_handle_vblank(crtc);
> +	if (!succ)
> +		return HRTIMER_NORESTART;
> +
> +	return HRTIMER_RESTART;
> +}
> +
> +static void drmm_vblank_timer_release(struct drm_device *dev, void *res)
> +{
> +	struct drm_vblank_timer *vtimer = res;
> +
> +	hrtimer_cancel(&vtimer->timer);
> +}
> +
> +int drmm_vblank_timer_init(struct drm_vblank_timer *vtimer, struct drm_crtc *crtc,
> +			   bool (*crtc_handle_vblank)(struct drm_crtc *crtc))
> +{
> +	struct hrtimer *timer = &vtimer->timer;
> +
> +	vtimer->crtc = crtc;
> +	vtimer->crtc_handle_vblank = crtc_handle_vblank;
> +
> +	hrtimer_setup(timer, drm_vblank_timer_function, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
> +
> +	return drmm_add_action_or_reset(crtc->dev, drmm_vblank_timer_release, vtimer);
> +}
> +EXPORT_SYMBOL(drmm_vblank_timer_init);
> +
> +void drm_vblank_timer_start(struct drm_vblank_timer *vtimer)
> +{
> +	struct drm_crtc *crtc = vtimer->crtc;
> +	struct drm_vblank_crtc *vblank = drm_crtc_vblank_crtc(crtc);
> +
> +	drm_calc_timestamping_constants(crtc, &crtc->mode);
> +
> +	vtimer->period_ns = ktime_set(0, vblank->framedur_ns);
> +	hrtimer_start(&vtimer->timer, vtimer->period_ns, HRTIMER_MODE_REL);
> +}
> +EXPORT_SYMBOL(drm_vblank_timer_start);
> +
> +void drm_vblank_timer_cancel(struct drm_vblank_timer *vtimer)
> +{
> +	hrtimer_cancel(&vtimer->timer);
> +}
> +EXPORT_SYMBOL(drm_vblank_timer_cancel);
> +
> +bool drm_vblank_timer_get_vblank_timestamp(struct drm_vblank_timer *vtimer,
> +					   int *max_error, ktime_t *vblank_time,
> +					   bool in_vblank_irq)
> +{
> +	struct drm_crtc *crtc = vtimer->crtc;
> +	struct drm_vblank_crtc *vblank = drm_crtc_vblank_crtc(crtc);
> +
> +	if (!READ_ONCE(vblank->enabled)) {
> +		*vblank_time = ktime_get();
> +		return true;
> +	}
> +
> +	*vblank_time = READ_ONCE(vtimer->timer.node.expires);
> +
> +	if (WARN_ON(*vblank_time == vblank->time))
> +		return true;
> +
> +	/*
> +	 * 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
> +	 * timestampe by one frame.
> +	 */
> +	*vblank_time -= vtimer->period_ns;
> +
> +	return true;
> +}
> +EXPORT_SYMBOL(drm_vblank_timer_get_vblank_timestamp);
> diff --git a/include/drm/drm_vblank_timer.h b/include/drm/drm_vblank_timer.h
> new file mode 100644
> index 000000000000..0b827ff1f59c
> --- /dev/null
> +++ b/include/drm/drm_vblank_timer.h
> @@ -0,0 +1,26 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +
> +#ifndef _DRM_VBLANK_TIMER_H_
> +#define _DRM_VBLANK_TIMER_H_
> +
> +#include <linux/hrtimer_types.h>
> +#include <linux/types.h>
> +
> +struct drm_crtc;
> +
> +struct drm_vblank_timer {
> +	struct drm_crtc *crtc;
> +	bool (*crtc_handle_vblank)(struct drm_crtc *crtc);
> +	ktime_t period_ns;
> +	struct hrtimer timer;
> +};
> +
> +int drmm_vblank_timer_init(struct drm_vblank_timer *vtimer, struct drm_crtc *crtc,
> +			   bool (*handle_vblank)(struct drm_crtc *crtc));
> +void drm_vblank_timer_start(struct drm_vblank_timer *vtimer);
> +void drm_vblank_timer_cancel(struct drm_vblank_timer *vtimer);
> +bool drm_vblank_timer_get_vblank_timestamp(struct drm_vblank_timer *vtimer,
> +					   int *max_error, ktime_t *vblank_time,
> +					   bool in_vblank_irq);
> +
> +#endif

-- 
Louis Chauvet, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH 2/6] drm/vkms: Use vblank timer
  2025-06-05 15:24 ` [PATCH 2/6] drm/vkms: Use " Thomas Zimmermann
@ 2025-06-06  9:14   ` Louis Chauvet
  0 siblings, 0 replies; 10+ messages in thread
From: Louis Chauvet @ 2025-06-06  9:14 UTC (permalink / raw)
  To: Thomas Zimmermann, mhklinux, maarten.lankhorst, mripard, airlied,
	simona, drawat.floss, javierm, kraxel, hamohammed.sa, melissa.srw,
	fvogt
  Cc: dri-devel, linux-hyperv, virtualization



Le 05/06/2025 à 17:24, Thomas Zimmermann a écrit :
> Replace vkms' vblank timer with the DRM implementation. The DRM
> code is mostly identical.
> 
> Signed-off-by: Thomas Zimmermann <tzimmermann@suse.de>

Reviewed-by: Louis Chauvet <louis.chauvet@bootlin.com>
Tested-by: Louis Chauvet <louis.chauvet@bootlin>

> ---
>   drivers/gpu/drm/vkms/vkms_crtc.c | 49 +++++++-------------------------
>   drivers/gpu/drm/vkms/vkms_drv.h  |  6 ++--
>   2 files changed, 15 insertions(+), 40 deletions(-)
> 
> diff --git a/drivers/gpu/drm/vkms/vkms_crtc.c b/drivers/gpu/drm/vkms/vkms_crtc.c
> index 8c9898b9055d..5b7829e8c900 100644
> --- a/drivers/gpu/drm/vkms/vkms_crtc.c
> +++ b/drivers/gpu/drm/vkms/vkms_crtc.c
> @@ -10,22 +10,14 @@
>   
>   #include "vkms_drv.h"
>   
> -static enum hrtimer_restart vkms_vblank_simulate(struct hrtimer *timer)
> +static bool vkms_crtc_handle_vblank(struct drm_crtc *crtc)
>   {
> -	struct vkms_output *output = container_of(timer, struct vkms_output,
> -						  vblank_hrtimer);
> -	struct drm_crtc *crtc = &output->crtc;
> +	struct vkms_output *output = drm_crtc_to_vkms_output(crtc);
>   	struct vkms_crtc_state *state;
> -	u64 ret_overrun;
>   	bool ret, fence_cookie;
>   
>   	fence_cookie = dma_fence_begin_signalling();
>   
> -	ret_overrun = hrtimer_forward_now(&output->vblank_hrtimer,
> -					  output->period_ns);
> -	if (ret_overrun != 1)
> -		pr_warn("%s: vblank timer overrun\n", __func__);
> -
>   	spin_lock(&output->lock);
>   	ret = drm_crtc_handle_vblank(crtc);
>   	if (!ret)
> @@ -57,18 +49,14 @@ static enum hrtimer_restart vkms_vblank_simulate(struct hrtimer *timer)
>   
>   	dma_fence_end_signalling(fence_cookie);
>   
> -	return HRTIMER_RESTART;
> +	return true;
>   }
>   
>   static int vkms_enable_vblank(struct drm_crtc *crtc)
>   {
> -	struct drm_vblank_crtc *vblank = drm_crtc_vblank_crtc(crtc);
>   	struct vkms_output *out = drm_crtc_to_vkms_output(crtc);
>   
> -	hrtimer_setup(&out->vblank_hrtimer, &vkms_vblank_simulate, CLOCK_MONOTONIC,
> -		      HRTIMER_MODE_REL);
> -	out->period_ns = ktime_set(0, vblank->framedur_ns);
> -	hrtimer_start(&out->vblank_hrtimer, out->period_ns, HRTIMER_MODE_REL);
> +	drm_vblank_timer_start(&out->vtimer);
>   
>   	return 0;
>   }
> @@ -77,7 +65,7 @@ static void vkms_disable_vblank(struct drm_crtc *crtc)
>   {
>   	struct vkms_output *out = drm_crtc_to_vkms_output(crtc);
>   
> -	hrtimer_cancel(&out->vblank_hrtimer);
> +	drm_vblank_timer_cancel(&out->vtimer);
>   }
>   
>   static bool vkms_get_vblank_timestamp(struct drm_crtc *crtc,
> @@ -85,28 +73,9 @@ static bool vkms_get_vblank_timestamp(struct drm_crtc *crtc,
>   				      bool in_vblank_irq)
>   {
>   	struct vkms_output *output = drm_crtc_to_vkms_output(crtc);
> -	struct drm_vblank_crtc *vblank = drm_crtc_vblank_crtc(crtc);
> -
> -	if (!READ_ONCE(vblank->enabled)) {
> -		*vblank_time = ktime_get();
> -		return true;
> -	}
> -
> -	*vblank_time = READ_ONCE(output->vblank_hrtimer.node.expires);
>   
> -	if (WARN_ON(*vblank_time == vblank->time))
> -		return true;
> -
> -	/*
> -	 * 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
> -	 * timestampe by one frame.
> -	 */
> -	*vblank_time -= output->period_ns;
> -
> -	return true;
> +	return drm_vblank_timer_get_vblank_timestamp(&output->vtimer, max_error,
> +						     vblank_time, in_vblank_irq);
>   }
>   
>   static struct drm_crtc_state *
> @@ -274,6 +243,7 @@ struct vkms_output *vkms_crtc_init(struct drm_device *dev, struct drm_plane *pri
>   {
>   	struct vkms_output *vkms_out;
>   	struct drm_crtc *crtc;
> +	struct drm_vblank_timer *vtimer;
>   	int ret;
>   
>   	vkms_out = drmm_crtc_alloc_with_planes(dev, struct vkms_output, crtc,
> @@ -285,6 +255,7 @@ struct vkms_output *vkms_crtc_init(struct drm_device *dev, struct drm_plane *pri
>   	}
>   
>   	crtc = &vkms_out->crtc;
> +	vtimer = &vkms_out->vtimer;
>   
>   	drm_crtc_helper_add(crtc, &vkms_crtc_helper_funcs);
>   
> @@ -305,5 +276,7 @@ struct vkms_output *vkms_crtc_init(struct drm_device *dev, struct drm_plane *pri
>   	if (!vkms_out->composer_workq)
>   		return ERR_PTR(-ENOMEM);
>   
> +	drmm_vblank_timer_init(vtimer, crtc, vkms_crtc_handle_vblank);
> +
>   	return vkms_out;
>   }
> diff --git a/drivers/gpu/drm/vkms/vkms_drv.h b/drivers/gpu/drm/vkms/vkms_drv.h
> index a74a7fc3a056..126016898285 100644
> --- a/drivers/gpu/drm/vkms/vkms_drv.h
> +++ b/drivers/gpu/drm/vkms/vkms_drv.h
> @@ -10,6 +10,7 @@
>   #include <drm/drm_gem.h>
>   #include <drm/drm_gem_atomic_helper.h>
>   #include <drm/drm_encoder.h>
> +#include <drm/drm_vblank_timer.h>
>   #include <drm/drm_writeback.h>
>   
>   #define DEFAULT_DEVICE_NAME "vkms"
> @@ -180,8 +181,9 @@ struct vkms_output {
>   	struct drm_crtc crtc;
>   	struct drm_writeback_connector wb_connector;
>   	struct drm_encoder wb_encoder;
> -	struct hrtimer vblank_hrtimer;
> -	ktime_t period_ns;
> +	struct drm_vblank_timer vtimer;
> +	struct drm_pending_vblank_event *event;
> +	/* ordered wq for composer_work */
>   	struct workqueue_struct *composer_workq;
>   	spinlock_t lock;
>   

-- 
Louis Chauvet, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH 1/6] drm/vblank: Add vblank timer
  2025-06-06  8:43   ` Louis Chauvet
@ 2025-06-10 15:36     ` Thomas Zimmermann
  0 siblings, 0 replies; 10+ messages in thread
From: Thomas Zimmermann @ 2025-06-10 15:36 UTC (permalink / raw)
  To: Louis Chauvet, mhklinux, maarten.lankhorst, mripard, airlied,
	simona, drawat.floss, javierm, kraxel, hamohammed.sa, melissa.srw,
	fvogt
  Cc: dri-devel, linux-hyperv, virtualization

Hi

Am 06.06.25 um 10:43 schrieb Louis Chauvet:
>
>
> Le 05/06/2025 à 17:24, Thomas Zimmermann a écrit :
>> 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").
>>
>> Signed-off-by: Thomas Zimmermann <tzimmermann@suse.de>
>
> Tested-by: Louis Chauvet <louis.chauvet@bootlin.com>
> Reviewed-by: Louis Chauvet <louis.chauvet@bootlin.com>

Thanks for looking through the series. I'll add the tags when I prepare 
the next iteration, but the interfaces will change quite a bit. You may 
want to take another look at the series then.

Best regards
Thomas

>
>> ---
>>   drivers/gpu/drm/Makefile           |   3 +-
>>   drivers/gpu/drm/drm_vblank_timer.c | 100 +++++++++++++++++++++++++++++
>>   include/drm/drm_vblank_timer.h     |  26 ++++++++
>>   3 files changed, 128 insertions(+), 1 deletion(-)
>>   create mode 100644 drivers/gpu/drm/drm_vblank_timer.c
>>   create mode 100644 include/drm/drm_vblank_timer.h
>>
>> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
>> index b5d5561bbe5f..6722e2d1aa7e 100644
>> --- a/drivers/gpu/drm/Makefile
>> +++ b/drivers/gpu/drm/Makefile
>> @@ -146,7 +146,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_timer.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
>> diff --git a/drivers/gpu/drm/drm_vblank_timer.c 
>> b/drivers/gpu/drm/drm_vblank_timer.c
>> new file mode 100644
>> index 000000000000..be46d3135c8e
>> --- /dev/null
>> +++ b/drivers/gpu/drm/drm_vblank_timer.c
>> @@ -0,0 +1,100 @@
>> +// SPDX-License-Identifier: GPL-2.0+
>> +
>> +#include <linux/hrtimer.h>
>> +
>> +#include <drm/drm_crtc.h>
>> +#include <drm/drm_managed.h>
>> +#include <drm/drm_print.h>
>> +#include <drm/drm_vblank.h>
>> +#include <drm/drm_vblank_timer.h>
>> +
>> +static enum hrtimer_restart drm_vblank_timer_function(struct hrtimer 
>> *timer)
>> +{
>> +    struct drm_vblank_timer *vtimer = container_of(timer, struct 
>> drm_vblank_timer, timer);
>> +    struct drm_crtc *crtc = vtimer->crtc;
>> +    struct drm_device *dev = crtc->dev;
>> +    u64 ret_overrun;
>> +    bool succ;
>> +
>> +    ret_overrun = hrtimer_forward_now(&vtimer->timer, 
>> vtimer->period_ns);
>> +    if (ret_overrun != 1)
>> +        drm_warn(dev, "vblank timer overrun\n");
>> +
>> +    if (vtimer->crtc_handle_vblank)
>> +        succ = vtimer->crtc_handle_vblank(crtc);
>> +    else
>> +        succ = drm_crtc_handle_vblank(crtc);
>> +    if (!succ)
>> +        return HRTIMER_NORESTART;
>> +
>> +    return HRTIMER_RESTART;
>> +}
>> +
>> +static void drmm_vblank_timer_release(struct drm_device *dev, void 
>> *res)
>> +{
>> +    struct drm_vblank_timer *vtimer = res;
>> +
>> +    hrtimer_cancel(&vtimer->timer);
>> +}
>> +
>> +int drmm_vblank_timer_init(struct drm_vblank_timer *vtimer, struct 
>> drm_crtc *crtc,
>> +               bool (*crtc_handle_vblank)(struct drm_crtc *crtc))
>> +{
>> +    struct hrtimer *timer = &vtimer->timer;
>> +
>> +    vtimer->crtc = crtc;
>> +    vtimer->crtc_handle_vblank = crtc_handle_vblank;
>> +
>> +    hrtimer_setup(timer, drm_vblank_timer_function, CLOCK_MONOTONIC, 
>> HRTIMER_MODE_REL);
>> +
>> +    return drmm_add_action_or_reset(crtc->dev, 
>> drmm_vblank_timer_release, vtimer);
>> +}
>> +EXPORT_SYMBOL(drmm_vblank_timer_init);
>> +
>> +void drm_vblank_timer_start(struct drm_vblank_timer *vtimer)
>> +{
>> +    struct drm_crtc *crtc = vtimer->crtc;
>> +    struct drm_vblank_crtc *vblank = drm_crtc_vblank_crtc(crtc);
>> +
>> +    drm_calc_timestamping_constants(crtc, &crtc->mode);
>> +
>> +    vtimer->period_ns = ktime_set(0, vblank->framedur_ns);
>> +    hrtimer_start(&vtimer->timer, vtimer->period_ns, HRTIMER_MODE_REL);
>> +}
>> +EXPORT_SYMBOL(drm_vblank_timer_start);
>> +
>> +void drm_vblank_timer_cancel(struct drm_vblank_timer *vtimer)
>> +{
>> +    hrtimer_cancel(&vtimer->timer);
>> +}
>> +EXPORT_SYMBOL(drm_vblank_timer_cancel);
>> +
>> +bool drm_vblank_timer_get_vblank_timestamp(struct drm_vblank_timer 
>> *vtimer,
>> +                       int *max_error, ktime_t *vblank_time,
>> +                       bool in_vblank_irq)
>> +{
>> +    struct drm_crtc *crtc = vtimer->crtc;
>> +    struct drm_vblank_crtc *vblank = drm_crtc_vblank_crtc(crtc);
>> +
>> +    if (!READ_ONCE(vblank->enabled)) {
>> +        *vblank_time = ktime_get();
>> +        return true;
>> +    }
>> +
>> +    *vblank_time = READ_ONCE(vtimer->timer.node.expires);
>> +
>> +    if (WARN_ON(*vblank_time == vblank->time))
>> +        return true;
>> +
>> +    /*
>> +     * 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
>> +     * timestampe by one frame.
>> +     */
>> +    *vblank_time -= vtimer->period_ns;
>> +
>> +    return true;
>> +}
>> +EXPORT_SYMBOL(drm_vblank_timer_get_vblank_timestamp);
>> diff --git a/include/drm/drm_vblank_timer.h 
>> b/include/drm/drm_vblank_timer.h
>> new file mode 100644
>> index 000000000000..0b827ff1f59c
>> --- /dev/null
>> +++ b/include/drm/drm_vblank_timer.h
>> @@ -0,0 +1,26 @@
>> +/* SPDX-License-Identifier: GPL-2.0+ */
>> +
>> +#ifndef _DRM_VBLANK_TIMER_H_
>> +#define _DRM_VBLANK_TIMER_H_
>> +
>> +#include <linux/hrtimer_types.h>
>> +#include <linux/types.h>
>> +
>> +struct drm_crtc;
>> +
>> +struct drm_vblank_timer {
>> +    struct drm_crtc *crtc;
>> +    bool (*crtc_handle_vblank)(struct drm_crtc *crtc);
>> +    ktime_t period_ns;
>> +    struct hrtimer timer;
>> +};
>> +
>> +int drmm_vblank_timer_init(struct drm_vblank_timer *vtimer, struct 
>> drm_crtc *crtc,
>> +               bool (*handle_vblank)(struct drm_crtc *crtc));
>> +void drm_vblank_timer_start(struct drm_vblank_timer *vtimer);
>> +void drm_vblank_timer_cancel(struct drm_vblank_timer *vtimer);
>> +bool drm_vblank_timer_get_vblank_timestamp(struct drm_vblank_timer 
>> *vtimer,
>> +                       int *max_error, ktime_t *vblank_time,
>> +                       bool in_vblank_irq);
>> +
>> +#endif
>

-- 
--
Thomas Zimmermann
Graphics Driver Developer
SUSE Software Solutions Germany GmbH
Frankenstrasse 146, 90461 Nuernberg, Germany
GF: Ivo Totev, Andrew Myers, Andrew McDonald, Boudien Moerman
HRB 36809 (AG Nuernberg)


^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2025-06-10 15:36 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-06-05 15:24 [RFC PATCH 0/6] drm: Add vblank timers for devices without interrupts Thomas Zimmermann
2025-06-05 15:24 ` [PATCH 1/6] drm/vblank: Add vblank timer Thomas Zimmermann
2025-06-06  8:43   ` Louis Chauvet
2025-06-10 15:36     ` Thomas Zimmermann
2025-06-05 15:24 ` [PATCH 2/6] drm/vkms: Use " Thomas Zimmermann
2025-06-06  9:14   ` Louis Chauvet
2025-06-05 15:24 ` [PATCH 3/6] drm/simpledrm: " Thomas Zimmermann
2025-06-05 15:24 ` [PATCH 4/6] drm/bochs: " Thomas Zimmermann
2025-06-05 15:24 ` [PATCH 5/6] drm/hypervdrm: " Thomas Zimmermann
2025-06-05 15:24 ` [PATCH 6/6] drm/fb-helper: Synchronize dirty worker with vblank Thomas Zimmermann

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).