* [PATCH v4] drm/vblank: Don't arm vblank timer with invalid frame duration
@ 2026-09-14 10:57 Thomas Zimmermann
2026-09-14 11:11 ` sashiko-bot
` (2 more replies)
0 siblings, 3 replies; 14+ messages in thread
From: Thomas Zimmermann @ 2026-09-14 10:57 UTC (permalink / raw)
To: me, louis.chauvet, maarten.lankhorst, mripard, airlied, simona,
javierm, dmitry.osipenko, ville.syrjala, regressions,
peter.m.arnesen
Cc: dri-devel, virtualization, sashiko-reviews, Thomas Zimmermann
From: Roman Ilin <me@romanilin.is>
When a CRTC's display mode carries a too small pixel clock,
drm_calc_timestamping_constants() computes a frame duration that
exceeds INT_MAX. drm_vblank_crtc.framedur_ns becomes negative.
drm_crtc_vblank_start_timer() then arms the vblank hrtimer with this
interval, after which vblank events are no longer delivered. Pending
page flips never complete and the display appears frozen.
This could be triggered on virtio-gpu guests that have dynamic resolution
enabled: when the SPICE agent or the X server resizes the output, it
submits a mode whose pixel clock is off by a factor of 1000, e.g.:
clock = 406 kHz, htotal = 3152, vtotal = 2148
framedur_ns = 3152 * 2148 * 1000000 / 406 = 16675852216 ns (~16.7 s)
16675852216 does not fit into an int and wraps to roughly -504000000.
ns_to_ktime() then yields a negative interval and the timer stops working.
Found by bisection, which pointed at commit a036f5fceedb ("drm/virtgpu:
Use vblank timer"). That commit merely made virtio-gpu use the vblank
timer and thereby exposed the pre-existing problem in the timer setup
added by commit 74afeb812850 ("drm/vblank: Add vblank timer").
To fix this, modify drm_calc_timestamping_constants() to use u64 for
calculations, check for INT_MAX overflows, and return an error code.
drm_crtc_vblank_start_timer() will then propagate the error, enabling
the driver to fall back to immediate vblank events. Valid modes are
unaffected, and the timer self-heals on the next mode with a sane clock.
v4:
- avoid log spamming with drm_warn_once()
Fixes: 74afeb812850 ("drm/vblank: Add vblank timer")
Suggested-by: Thomas Zimmermann <tzimmermann@suse.de>
Signed-off-by: Roman Ilin <me@romanilin.is>
---
I took Roman's patch from [1] and replaced the error messages with
warn_once, as suggestde in my review.
[1] https://lore.kernel.org/dri-devel/20260702181027.98526-1-me@romanilin.is/
---
drivers/gpu/drm/drm_vblank.c | 73 +++++++++++++++++++++++-------------
include/drm/drm_vblank.h | 4 +-
2 files changed, 49 insertions(+), 28 deletions(-)
diff --git a/drivers/gpu/drm/drm_vblank.c b/drivers/gpu/drm/drm_vblank.c
index f90fb2d13e42..72b543bca9e3 100644
--- a/drivers/gpu/drm/drm_vblank.c
+++ b/drivers/gpu/drm/drm_vblank.c
@@ -631,45 +631,54 @@ EXPORT_SYMBOL(drm_crtc_vblank_waitqueue);
* drm_crtc_vblank_helper_get_vblank_timestamp(). They are derived from
* CRTC's true scanout timing, so they take things like panel scaling or
* other adjustments into account.
+ *
+ * Returns:
+ * 0 on success, or a negative errno code otherwise.
*/
-void drm_calc_timestamping_constants(struct drm_crtc *crtc,
- const struct drm_display_mode *mode)
+int drm_calc_timestamping_constants(struct drm_crtc *crtc,
+ const struct drm_display_mode *mode)
{
struct drm_device *dev = crtc->dev;
unsigned int pipe = drm_crtc_index(crtc);
struct drm_vblank_crtc *vblank = drm_crtc_vblank_crtc(crtc);
- int linedur_ns = 0, framedur_ns = 0;
+ u64 linedur_ns, framedur_ns;
int dotclock = mode->crtc_clock;
+ unsigned int frame_size;
if (!drm_dev_has_vblank(dev))
- return;
+ return 0;
if (drm_WARN_ON(dev, pipe >= dev->num_crtcs))
- return;
+ return -EINVAL;
- /* Valid dotclock? */
- if (dotclock > 0) {
- int frame_size = mode->crtc_htotal * mode->crtc_vtotal;
+ if (dotclock <= 0) {
+ drm_err_once(dev, "crtc %u: Can't calculate constants, dotclock = %d!\n",
+ crtc->base.id, dotclock);
+ goto error;
+ }
- /*
- * Convert scanline length in pixels and video
- * dot clock to line duration and frame duration
- * in nanoseconds:
- */
- linedur_ns = div_u64((u64) mode->crtc_htotal * 1000000, dotclock);
- framedur_ns = div_u64((u64) frame_size * 1000000, dotclock);
+ frame_size = (unsigned int)mode->crtc_htotal * (unsigned int)mode->crtc_vtotal;
- /*
- * Fields of interlaced scanout modes are only half a frame duration.
- */
- if (mode->flags & DRM_MODE_FLAG_INTERLACE)
- framedur_ns /= 2;
- } else {
- drm_err(dev, "crtc %u: Can't calculate constants, dotclock = 0!\n",
- crtc->base.id);
+ /*
+ * Convert scanline length in pixels and video dot clock to line duration
+ * and frame duration in nanoseconds.
+ */
+ linedur_ns = div_u64((u64)mode->crtc_htotal * 1000000, dotclock);
+ framedur_ns = div_u64((u64)frame_size * 1000000, dotclock);
+
+ /*
+ * Fields of interlaced scanout modes are only half a frame duration.
+ */
+ if (mode->flags & DRM_MODE_FLAG_INTERLACE)
+ framedur_ns /= 2;
+
+ if (linedur_ns > INT_MAX || framedur_ns > INT_MAX) {
+ drm_err_once(dev, "crtc %u: Can't calculate constants, mode clock too small!\n",
+ crtc->base.id);
+ goto error;
}
- vblank->linedur_ns = linedur_ns;
+ vblank->linedur_ns = linedur_ns;
vblank->framedur_ns = framedur_ns;
drm_mode_copy(&vblank->hwmode, mode);
@@ -678,7 +687,16 @@ void drm_calc_timestamping_constants(struct drm_crtc *crtc,
crtc->base.id, mode->crtc_htotal,
mode->crtc_vtotal, mode->crtc_vdisplay);
drm_dbg_core(dev, "crtc %u: clock %d kHz framedur %d linedur %d\n",
- crtc->base.id, dotclock, framedur_ns, linedur_ns);
+ crtc->base.id, dotclock,
+ vblank->framedur_ns, vblank->linedur_ns);
+
+ return 0;
+
+error:
+ vblank->linedur_ns = 0;
+ vblank->framedur_ns = 0;
+ drm_mode_copy(&vblank->hwmode, mode);
+ return -EINVAL;
}
EXPORT_SYMBOL(drm_calc_timestamping_constants);
@@ -2221,6 +2239,7 @@ 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;
+ int ret;
if (!vtimer->crtc) {
/*
@@ -2239,7 +2258,9 @@ int drm_crtc_vblank_start_timer(struct drm_crtc *crtc)
hrtimer_try_to_cancel(&vtimer->timer);
}
- drm_calc_timestamping_constants(crtc, &crtc->mode);
+ ret = drm_calc_timestamping_constants(crtc, &crtc->mode);
+ if (ret)
+ return ret;
spin_lock_irqsave(&vtimer->interval_lock, flags);
vtimer->interval = ns_to_ktime(vblank->framedur_ns);
diff --git a/include/drm/drm_vblank.h b/include/drm/drm_vblank.h
index 2fcef9c0f5b1..d99772dfa034 100644
--- a/include/drm/drm_vblank.h
+++ b/include/drm/drm_vblank.h
@@ -311,8 +311,8 @@ void drm_crtc_vblank_on(struct drm_crtc *crtc);
u64 drm_crtc_accurate_vblank_count(struct drm_crtc *crtc);
void drm_crtc_vblank_restore(struct drm_crtc *crtc);
-void drm_calc_timestamping_constants(struct drm_crtc *crtc,
- const struct drm_display_mode *mode);
+int drm_calc_timestamping_constants(struct drm_crtc *crtc,
+ const struct drm_display_mode *mode);
wait_queue_head_t *drm_crtc_vblank_waitqueue(struct drm_crtc *crtc);
void drm_crtc_set_max_vblank_count(struct drm_crtc *crtc,
u32 max_vblank_count);
base-commit: 06576c2b43750d990297b8ff1989b94ac72e855f
prerequisite-patch-id: 6729bb10d5e338678e78ccda2dd24f232a13cd22
prerequisite-patch-id: 9d94d20bd45967493b4dc339b58082e1c8da0f4f
prerequisite-patch-id: 6ac8d22df34db9e7d467b1ab95ad30f94841d5db
prerequisite-patch-id: f548dbde3f944ca3415c808be628719acebe898a
prerequisite-patch-id: 2ae91ee8e2bbcd33b8325be046e7214185aba6aa
prerequisite-patch-id: f97bbde08a9996d7c62ca3e3ebfcd387e50bc5c3
prerequisite-patch-id: ecc3c825277bc9978c6979455517249a074c0cb1
prerequisite-patch-id: 0449925cca36664b566f614bfb7d573a11f0575f
prerequisite-patch-id: 9fb2c2787244e9621847af09bea03e575e6276c7
prerequisite-patch-id: eb4384d581805e1178d22e7df45f134223c38305
prerequisite-patch-id: 1a7a529e4b2864abe8071aa44598d28ce72483f4
prerequisite-patch-id: 844a85f4c2fe2f72209b82d46614004c3cfe4123
prerequisite-patch-id: 13f7cb22114c648d35f8c9fa74e9165656ce31eb
prerequisite-patch-id: 20ee46bd6fdd91e39f71d22f52ef6682a4e41512
prerequisite-patch-id: 1d26c0ff91796ff1e62d3831d00bee18723788ef
prerequisite-patch-id: 007f6ea0d09bef54b689bae0eb5abeb497bdfaad
prerequisite-patch-id: 2d89c1507c909520adf9bfb1a137d458a819ae8f
prerequisite-patch-id: d90dc4134468870ab8ccbd71547c9552dff74fbd
prerequisite-patch-id: c67e5d886a47b7d0266d81100837557fda34cb24
prerequisite-patch-id: a5a973e527c88a5b47053d7a72aefe0b550197cb
prerequisite-patch-id: b9adc9622920a3e70168e672c2c92795b3e3a106
prerequisite-patch-id: 5030de433a01c2e99056cadb676a8e2ba35f055a
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* Re: [PATCH v4] drm/vblank: Don't arm vblank timer with invalid frame duration 2026-09-14 10:57 [PATCH v4] drm/vblank: Don't arm vblank timer with invalid frame duration Thomas Zimmermann @ 2026-09-14 11:11 ` sashiko-bot 2026-10-28 7:29 ` Thomas Zimmermann 2026-10-28 14:54 ` Ville Syrjälä 2 siblings, 0 replies; 14+ messages in thread From: sashiko-bot @ 2026-09-14 11:11 UTC (permalink / raw) To: Thomas Zimmermann Cc: virtualization, Michael S. Tsirkin, dri-devel, Eugenio Perez Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: Pre-existing issues: - [High] AB-BA deadlock between synchronous hrtimer cancellation in drm_crtc_vblank_start_timer() and drm_handle_vblank() taking vblank_time_lock. -- commit de00db596b26306bffcd319a867015e9f4b1faa6 Author: Roman Ilin <me@romanilin.is> drm/vblank: Don't arm vblank timer with invalid frame duration This commit modifies drm_calc_timestamping_constants() to check for INT_MAX overflows and returns an error instead of proceeding with invalid frame durations. It also updates drm_crtc_vblank_start_timer() to propagate this error, allowing the driver to fall back to immediate vblank events. > diff --git a/drivers/gpu/drm/drm_vblank.c b/drivers/gpu/drm/drm_vblank.c > index f90fb2d13e423..72b543bca9e31 100644 > --- a/drivers/gpu/drm/drm_vblank.c > +++ b/drivers/gpu/drm/drm_vblank.c [ ... ] > @@ -2221,6 +2239,7 @@ 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; > + int ret; > > if (!vtimer->crtc) { > /* > @@ -2239,7 +2258,9 @@ int drm_crtc_vblank_start_timer(struct drm_crtc *crtc) > hrtimer_try_to_cancel(&vtimer->timer); [Severity: High] This is a pre-existing issue, but can this busy-wait loop cause a hard deadlock? When CPU 1 enables vblanks, it holds the vblank_time_lock while executing this synchronous cancellation loop: CPU 1: drm_vblank_enable() { spin_lock(&dev->vblank_time_lock); __enable_vblank(dev, pipe); ... drm_crtc_vblank_start_timer(crtc); while (hrtimer_active(&vtimer->timer)) hrtimer_try_to_cancel(&vtimer->timer); If the timer has just fired concurrently, CPU 2 executes the callback which attempts to acquire the same lock: CPU 2: drm_vblank_timer_function() { ... drm_handle_vblank(dev, pipe); spin_lock(&dev->vblank_time_lock); This creates an AB-BA deadlock where CPU 1 spins forever waiting for the callback to finish on CPU 2, while CPU 2 spins forever waiting for CPU 1 to release vblank_time_lock. Is it safe to synchronously cancel the timer here while holding a lock that the timer's callback also takes? > } > > - drm_calc_timestamping_constants(crtc, &crtc->mode); > + ret = drm_calc_timestamping_constants(crtc, &crtc->mode); > + if (ret) > + return ret; > > spin_lock_irqsave(&vtimer->interval_lock, flags); > vtimer->interval = ns_to_ktime(vblank->framedur_ns); -- Sashiko AI review · https://sashiko.dev/#/patchset/20260914110004.211027-1-tzimmermann@suse.de?part=1 ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v4] drm/vblank: Don't arm vblank timer with invalid frame duration 2026-09-14 10:57 [PATCH v4] drm/vblank: Don't arm vblank timer with invalid frame duration Thomas Zimmermann 2026-09-14 11:11 ` sashiko-bot @ 2026-10-28 7:29 ` Thomas Zimmermann 2026-10-28 12:15 ` Dmitry Osipenko 2026-10-28 14:54 ` Ville Syrjälä 2 siblings, 1 reply; 14+ messages in thread From: Thomas Zimmermann @ 2026-10-28 7:29 UTC (permalink / raw) To: me, louis.chauvet, maarten.lankhorst, mripard, airlied, simona, javierm, dmitry.osipenko, ville.syrjala, regressions, peter.m.arnesen Cc: dri-devel, virtualization, sashiko-reviews Ping. Please review! Am 14.09.26 um 12:57 schrieb Thomas Zimmermann: > From: Roman Ilin <me@romanilin.is> > > When a CRTC's display mode carries a too small pixel clock, > drm_calc_timestamping_constants() computes a frame duration that > exceeds INT_MAX. drm_vblank_crtc.framedur_ns becomes negative. > drm_crtc_vblank_start_timer() then arms the vblank hrtimer with this > interval, after which vblank events are no longer delivered. Pending > page flips never complete and the display appears frozen. > > This could be triggered on virtio-gpu guests that have dynamic resolution > enabled: when the SPICE agent or the X server resizes the output, it > submits a mode whose pixel clock is off by a factor of 1000, e.g.: > > clock = 406 kHz, htotal = 3152, vtotal = 2148 > > framedur_ns = 3152 * 2148 * 1000000 / 406 = 16675852216 ns (~16.7 s) > > 16675852216 does not fit into an int and wraps to roughly -504000000. > ns_to_ktime() then yields a negative interval and the timer stops working. > > Found by bisection, which pointed at commit a036f5fceedb ("drm/virtgpu: > Use vblank timer"). That commit merely made virtio-gpu use the vblank > timer and thereby exposed the pre-existing problem in the timer setup > added by commit 74afeb812850 ("drm/vblank: Add vblank timer"). > > To fix this, modify drm_calc_timestamping_constants() to use u64 for > calculations, check for INT_MAX overflows, and return an error code. > drm_crtc_vblank_start_timer() will then propagate the error, enabling > the driver to fall back to immediate vblank events. Valid modes are > unaffected, and the timer self-heals on the next mode with a sane clock. > > v4: > - avoid log spamming with drm_warn_once() > > Fixes: 74afeb812850 ("drm/vblank: Add vblank timer") > Suggested-by: Thomas Zimmermann <tzimmermann@suse.de> > Signed-off-by: Roman Ilin <me@romanilin.is> > --- > I took Roman's patch from [1] and replaced the error messages with > warn_once, as suggestde in my review. > > [1] https://lore.kernel.org/dri-devel/20260702181027.98526-1-me@romanilin.is/ > --- > drivers/gpu/drm/drm_vblank.c | 73 +++++++++++++++++++++++------------- > include/drm/drm_vblank.h | 4 +- > 2 files changed, 49 insertions(+), 28 deletions(-) > > diff --git a/drivers/gpu/drm/drm_vblank.c b/drivers/gpu/drm/drm_vblank.c > index f90fb2d13e42..72b543bca9e3 100644 > --- a/drivers/gpu/drm/drm_vblank.c > +++ b/drivers/gpu/drm/drm_vblank.c > @@ -631,45 +631,54 @@ EXPORT_SYMBOL(drm_crtc_vblank_waitqueue); > * drm_crtc_vblank_helper_get_vblank_timestamp(). They are derived from > * CRTC's true scanout timing, so they take things like panel scaling or > * other adjustments into account. > + * > + * Returns: > + * 0 on success, or a negative errno code otherwise. > */ > -void drm_calc_timestamping_constants(struct drm_crtc *crtc, > - const struct drm_display_mode *mode) > +int drm_calc_timestamping_constants(struct drm_crtc *crtc, > + const struct drm_display_mode *mode) > { > struct drm_device *dev = crtc->dev; > unsigned int pipe = drm_crtc_index(crtc); > struct drm_vblank_crtc *vblank = drm_crtc_vblank_crtc(crtc); > - int linedur_ns = 0, framedur_ns = 0; > + u64 linedur_ns, framedur_ns; > int dotclock = mode->crtc_clock; > + unsigned int frame_size; > > if (!drm_dev_has_vblank(dev)) > - return; > + return 0; > > if (drm_WARN_ON(dev, pipe >= dev->num_crtcs)) > - return; > + return -EINVAL; > > - /* Valid dotclock? */ > - if (dotclock > 0) { > - int frame_size = mode->crtc_htotal * mode->crtc_vtotal; > + if (dotclock <= 0) { > + drm_err_once(dev, "crtc %u: Can't calculate constants, dotclock = %d!\n", > + crtc->base.id, dotclock); > + goto error; > + } > > - /* > - * Convert scanline length in pixels and video > - * dot clock to line duration and frame duration > - * in nanoseconds: > - */ > - linedur_ns = div_u64((u64) mode->crtc_htotal * 1000000, dotclock); > - framedur_ns = div_u64((u64) frame_size * 1000000, dotclock); > + frame_size = (unsigned int)mode->crtc_htotal * (unsigned int)mode->crtc_vtotal; > > - /* > - * Fields of interlaced scanout modes are only half a frame duration. > - */ > - if (mode->flags & DRM_MODE_FLAG_INTERLACE) > - framedur_ns /= 2; > - } else { > - drm_err(dev, "crtc %u: Can't calculate constants, dotclock = 0!\n", > - crtc->base.id); > + /* > + * Convert scanline length in pixels and video dot clock to line duration > + * and frame duration in nanoseconds. > + */ > + linedur_ns = div_u64((u64)mode->crtc_htotal * 1000000, dotclock); > + framedur_ns = div_u64((u64)frame_size * 1000000, dotclock); > + > + /* > + * Fields of interlaced scanout modes are only half a frame duration. > + */ > + if (mode->flags & DRM_MODE_FLAG_INTERLACE) > + framedur_ns /= 2; > + > + if (linedur_ns > INT_MAX || framedur_ns > INT_MAX) { > + drm_err_once(dev, "crtc %u: Can't calculate constants, mode clock too small!\n", > + crtc->base.id); > + goto error; > } > > - vblank->linedur_ns = linedur_ns; > + vblank->linedur_ns = linedur_ns; > vblank->framedur_ns = framedur_ns; > drm_mode_copy(&vblank->hwmode, mode); > > @@ -678,7 +687,16 @@ void drm_calc_timestamping_constants(struct drm_crtc *crtc, > crtc->base.id, mode->crtc_htotal, > mode->crtc_vtotal, mode->crtc_vdisplay); > drm_dbg_core(dev, "crtc %u: clock %d kHz framedur %d linedur %d\n", > - crtc->base.id, dotclock, framedur_ns, linedur_ns); > + crtc->base.id, dotclock, > + vblank->framedur_ns, vblank->linedur_ns); > + > + return 0; > + > +error: > + vblank->linedur_ns = 0; > + vblank->framedur_ns = 0; > + drm_mode_copy(&vblank->hwmode, mode); > + return -EINVAL; > } > EXPORT_SYMBOL(drm_calc_timestamping_constants); > > @@ -2221,6 +2239,7 @@ 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; > + int ret; > > if (!vtimer->crtc) { > /* > @@ -2239,7 +2258,9 @@ int drm_crtc_vblank_start_timer(struct drm_crtc *crtc) > hrtimer_try_to_cancel(&vtimer->timer); > } > > - drm_calc_timestamping_constants(crtc, &crtc->mode); > + ret = drm_calc_timestamping_constants(crtc, &crtc->mode); > + if (ret) > + return ret; > > spin_lock_irqsave(&vtimer->interval_lock, flags); > vtimer->interval = ns_to_ktime(vblank->framedur_ns); > diff --git a/include/drm/drm_vblank.h b/include/drm/drm_vblank.h > index 2fcef9c0f5b1..d99772dfa034 100644 > --- a/include/drm/drm_vblank.h > +++ b/include/drm/drm_vblank.h > @@ -311,8 +311,8 @@ void drm_crtc_vblank_on(struct drm_crtc *crtc); > u64 drm_crtc_accurate_vblank_count(struct drm_crtc *crtc); > void drm_crtc_vblank_restore(struct drm_crtc *crtc); > > -void drm_calc_timestamping_constants(struct drm_crtc *crtc, > - const struct drm_display_mode *mode); > +int drm_calc_timestamping_constants(struct drm_crtc *crtc, > + const struct drm_display_mode *mode); > wait_queue_head_t *drm_crtc_vblank_waitqueue(struct drm_crtc *crtc); > void drm_crtc_set_max_vblank_count(struct drm_crtc *crtc, > u32 max_vblank_count); > > base-commit: 06576c2b43750d990297b8ff1989b94ac72e855f > prerequisite-patch-id: 6729bb10d5e338678e78ccda2dd24f232a13cd22 > prerequisite-patch-id: 9d94d20bd45967493b4dc339b58082e1c8da0f4f > prerequisite-patch-id: 6ac8d22df34db9e7d467b1ab95ad30f94841d5db > prerequisite-patch-id: f548dbde3f944ca3415c808be628719acebe898a > prerequisite-patch-id: 2ae91ee8e2bbcd33b8325be046e7214185aba6aa > prerequisite-patch-id: f97bbde08a9996d7c62ca3e3ebfcd387e50bc5c3 > prerequisite-patch-id: ecc3c825277bc9978c6979455517249a074c0cb1 > prerequisite-patch-id: 0449925cca36664b566f614bfb7d573a11f0575f > prerequisite-patch-id: 9fb2c2787244e9621847af09bea03e575e6276c7 > prerequisite-patch-id: eb4384d581805e1178d22e7df45f134223c38305 > prerequisite-patch-id: 1a7a529e4b2864abe8071aa44598d28ce72483f4 > prerequisite-patch-id: 844a85f4c2fe2f72209b82d46614004c3cfe4123 > prerequisite-patch-id: 13f7cb22114c648d35f8c9fa74e9165656ce31eb > prerequisite-patch-id: 20ee46bd6fdd91e39f71d22f52ef6682a4e41512 > prerequisite-patch-id: 1d26c0ff91796ff1e62d3831d00bee18723788ef > prerequisite-patch-id: 007f6ea0d09bef54b689bae0eb5abeb497bdfaad > prerequisite-patch-id: 2d89c1507c909520adf9bfb1a137d458a819ae8f > prerequisite-patch-id: d90dc4134468870ab8ccbd71547c9552dff74fbd > prerequisite-patch-id: c67e5d886a47b7d0266d81100837557fda34cb24 > prerequisite-patch-id: a5a973e527c88a5b47053d7a72aefe0b550197cb > prerequisite-patch-id: b9adc9622920a3e70168e672c2c92795b3e3a106 > prerequisite-patch-id: 5030de433a01c2e99056cadb676a8e2ba35f055a -- -- Thomas Zimmermann Graphics Driver Developer SUSE Software Solutions Germany GmbH Frankenstr. 146, 90461 Nürnberg, Germany, www.suse.com GF: Stefan Gaiser, Jochen Jaser, Abhinav Puri, (HRB 36809, AG Nürnberg) ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v4] drm/vblank: Don't arm vblank timer with invalid frame duration 2026-10-28 7:29 ` Thomas Zimmermann @ 2026-10-28 12:15 ` Dmitry Osipenko 0 siblings, 0 replies; 14+ messages in thread From: Dmitry Osipenko @ 2026-10-28 12:15 UTC (permalink / raw) To: Thomas Zimmermann, me, louis.chauvet, maarten.lankhorst, mripard, airlied, simona, javierm, ville.syrjala, regressions, peter.m.arnesen Cc: dri-devel, virtualization, sashiko-reviews On 10/28/26 10:29, Thomas Zimmermann wrote: > > Ping. Please review! > > Am 14.09.26 um 12:57 schrieb Thomas Zimmermann: >> From: Roman Ilin <me@romanilin.is> >> >> When a CRTC's display mode carries a too small pixel clock, >> drm_calc_timestamping_constants() computes a frame duration that >> exceeds INT_MAX. drm_vblank_crtc.framedur_ns becomes negative. >> drm_crtc_vblank_start_timer() then arms the vblank hrtimer with this >> interval, after which vblank events are no longer delivered. Pending >> page flips never complete and the display appears frozen. >> >> This could be triggered on virtio-gpu guests that have dynamic resolution >> enabled: when the SPICE agent or the X server resizes the output, it >> submits a mode whose pixel clock is off by a factor of 1000, e.g.: >> >> clock = 406 kHz, htotal = 3152, vtotal = 2148 >> >> framedur_ns = 3152 * 2148 * 1000000 / 406 = 16675852216 ns (~16.7 s) >> >> 16675852216 does not fit into an int and wraps to roughly -504000000. >> ns_to_ktime() then yields a negative interval and the timer stops >> working. >> >> Found by bisection, which pointed at commit a036f5fceedb ("drm/virtgpu: >> Use vblank timer"). That commit merely made virtio-gpu use the vblank >> timer and thereby exposed the pre-existing problem in the timer setup >> added by commit 74afeb812850 ("drm/vblank: Add vblank timer"). >> >> To fix this, modify drm_calc_timestamping_constants() to use u64 for >> calculations, check for INT_MAX overflows, and return an error code. >> drm_crtc_vblank_start_timer() will then propagate the error, enabling >> the driver to fall back to immediate vblank events. Valid modes are >> unaffected, and the timer self-heals on the next mode with a sane clock. >> >> v4: >> - avoid log spamming with drm_warn_once() >> >> Fixes: 74afeb812850 ("drm/vblank: Add vblank timer") >> Suggested-by: Thomas Zimmermann <tzimmermann@suse.de> >> Signed-off-by: Roman Ilin <me@romanilin.is> >> --- >> I took Roman's patch from [1] and replaced the error messages with >> warn_once, as suggestde in my review. >> >> [1] https://lore.kernel.org/dri-devel/20260702181027.98526-1- >> me@romanilin.is/ >> --- >> drivers/gpu/drm/drm_vblank.c | 73 +++++++++++++++++++++++------------- >> include/drm/drm_vblank.h | 4 +- >> 2 files changed, 49 insertions(+), 28 deletions(-) >> >> diff --git a/drivers/gpu/drm/drm_vblank.c b/drivers/gpu/drm/drm_vblank.c >> index f90fb2d13e42..72b543bca9e3 100644 >> --- a/drivers/gpu/drm/drm_vblank.c >> +++ b/drivers/gpu/drm/drm_vblank.c >> @@ -631,45 +631,54 @@ EXPORT_SYMBOL(drm_crtc_vblank_waitqueue); >> * drm_crtc_vblank_helper_get_vblank_timestamp(). They are derived from >> * CRTC's true scanout timing, so they take things like panel >> scaling or >> * other adjustments into account. >> + * >> + * Returns: >> + * 0 on success, or a negative errno code otherwise. >> */ >> -void drm_calc_timestamping_constants(struct drm_crtc *crtc, >> - const struct drm_display_mode *mode) >> +int drm_calc_timestamping_constants(struct drm_crtc *crtc, >> + const struct drm_display_mode *mode) >> { >> struct drm_device *dev = crtc->dev; >> unsigned int pipe = drm_crtc_index(crtc); >> struct drm_vblank_crtc *vblank = drm_crtc_vblank_crtc(crtc); >> - int linedur_ns = 0, framedur_ns = 0; >> + u64 linedur_ns, framedur_ns; >> int dotclock = mode->crtc_clock; >> + unsigned int frame_size; >> if (!drm_dev_has_vblank(dev)) >> - return; >> + return 0; >> if (drm_WARN_ON(dev, pipe >= dev->num_crtcs)) >> - return; >> + return -EINVAL; >> - /* Valid dotclock? */ >> - if (dotclock > 0) { >> - int frame_size = mode->crtc_htotal * mode->crtc_vtotal; >> + if (dotclock <= 0) { >> + drm_err_once(dev, "crtc %u: Can't calculate constants, >> dotclock = %d!\n", >> + crtc->base.id, dotclock); >> + goto error; >> + } >> - /* >> - * Convert scanline length in pixels and video >> - * dot clock to line duration and frame duration >> - * in nanoseconds: >> - */ >> - linedur_ns = div_u64((u64) mode->crtc_htotal * 1000000, >> dotclock); >> - framedur_ns = div_u64((u64) frame_size * 1000000, dotclock); >> + frame_size = (unsigned int)mode->crtc_htotal * (unsigned >> int)mode->crtc_vtotal; >> - /* >> - * Fields of interlaced scanout modes are only half a frame >> duration. >> - */ >> - if (mode->flags & DRM_MODE_FLAG_INTERLACE) >> - framedur_ns /= 2; >> - } else { >> - drm_err(dev, "crtc %u: Can't calculate constants, dotclock = >> 0!\n", >> - crtc->base.id); >> + /* >> + * Convert scanline length in pixels and video dot clock to line >> duration >> + * and frame duration in nanoseconds. >> + */ >> + linedur_ns = div_u64((u64)mode->crtc_htotal * 1000000, dotclock); >> + framedur_ns = div_u64((u64)frame_size * 1000000, dotclock); >> + >> + /* >> + * Fields of interlaced scanout modes are only half a frame >> duration. >> + */ >> + if (mode->flags & DRM_MODE_FLAG_INTERLACE) >> + framedur_ns /= 2; >> + >> + if (linedur_ns > INT_MAX || framedur_ns > INT_MAX) { >> + drm_err_once(dev, "crtc %u: Can't calculate constants, mode >> clock too small!\n", >> + crtc->base.id); >> + goto error; >> } >> - vblank->linedur_ns = linedur_ns; >> + vblank->linedur_ns = linedur_ns; >> vblank->framedur_ns = framedur_ns; >> drm_mode_copy(&vblank->hwmode, mode); >> @@ -678,7 +687,16 @@ void drm_calc_timestamping_constants(struct >> drm_crtc *crtc, >> crtc->base.id, mode->crtc_htotal, >> mode->crtc_vtotal, mode->crtc_vdisplay); >> drm_dbg_core(dev, "crtc %u: clock %d kHz framedur %d linedur %d\n", >> - crtc->base.id, dotclock, framedur_ns, linedur_ns); >> + crtc->base.id, dotclock, >> + vblank->framedur_ns, vblank->linedur_ns); >> + >> + return 0; >> + >> +error: >> + vblank->linedur_ns = 0; >> + vblank->framedur_ns = 0; >> + drm_mode_copy(&vblank->hwmode, mode); >> + return -EINVAL; >> } >> EXPORT_SYMBOL(drm_calc_timestamping_constants); >> @@ -2221,6 +2239,7 @@ 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; >> + int ret; >> if (!vtimer->crtc) { >> /* >> @@ -2239,7 +2258,9 @@ int drm_crtc_vblank_start_timer(struct drm_crtc >> *crtc) >> hrtimer_try_to_cancel(&vtimer->timer); >> } >> - drm_calc_timestamping_constants(crtc, &crtc->mode); >> + ret = drm_calc_timestamping_constants(crtc, &crtc->mode); >> + if (ret) >> + return ret; >> spin_lock_irqsave(&vtimer->interval_lock, flags); >> vtimer->interval = ns_to_ktime(vblank->framedur_ns); >> diff --git a/include/drm/drm_vblank.h b/include/drm/drm_vblank.h >> index 2fcef9c0f5b1..d99772dfa034 100644 >> --- a/include/drm/drm_vblank.h >> +++ b/include/drm/drm_vblank.h >> @@ -311,8 +311,8 @@ void drm_crtc_vblank_on(struct drm_crtc *crtc); >> u64 drm_crtc_accurate_vblank_count(struct drm_crtc *crtc); >> void drm_crtc_vblank_restore(struct drm_crtc *crtc); >> -void drm_calc_timestamping_constants(struct drm_crtc *crtc, >> - const struct drm_display_mode *mode); >> +int drm_calc_timestamping_constants(struct drm_crtc *crtc, >> + const struct drm_display_mode *mode); >> wait_queue_head_t *drm_crtc_vblank_waitqueue(struct drm_crtc *crtc); >> void drm_crtc_set_max_vblank_count(struct drm_crtc *crtc, >> u32 max_vblank_count); >> >> base-commit: 06576c2b43750d990297b8ff1989b94ac72e855f >> prerequisite-patch-id: 6729bb10d5e338678e78ccda2dd24f232a13cd22 >> prerequisite-patch-id: 9d94d20bd45967493b4dc339b58082e1c8da0f4f >> prerequisite-patch-id: 6ac8d22df34db9e7d467b1ab95ad30f94841d5db >> prerequisite-patch-id: f548dbde3f944ca3415c808be628719acebe898a >> prerequisite-patch-id: 2ae91ee8e2bbcd33b8325be046e7214185aba6aa >> prerequisite-patch-id: f97bbde08a9996d7c62ca3e3ebfcd387e50bc5c3 >> prerequisite-patch-id: ecc3c825277bc9978c6979455517249a074c0cb1 >> prerequisite-patch-id: 0449925cca36664b566f614bfb7d573a11f0575f >> prerequisite-patch-id: 9fb2c2787244e9621847af09bea03e575e6276c7 >> prerequisite-patch-id: eb4384d581805e1178d22e7df45f134223c38305 >> prerequisite-patch-id: 1a7a529e4b2864abe8071aa44598d28ce72483f4 >> prerequisite-patch-id: 844a85f4c2fe2f72209b82d46614004c3cfe4123 >> prerequisite-patch-id: 13f7cb22114c648d35f8c9fa74e9165656ce31eb >> prerequisite-patch-id: 20ee46bd6fdd91e39f71d22f52ef6682a4e41512 >> prerequisite-patch-id: 1d26c0ff91796ff1e62d3831d00bee18723788ef >> prerequisite-patch-id: 007f6ea0d09bef54b689bae0eb5abeb497bdfaad >> prerequisite-patch-id: 2d89c1507c909520adf9bfb1a137d458a819ae8f >> prerequisite-patch-id: d90dc4134468870ab8ccbd71547c9552dff74fbd >> prerequisite-patch-id: c67e5d886a47b7d0266d81100837557fda34cb24 >> prerequisite-patch-id: a5a973e527c88a5b47053d7a72aefe0b550197cb >> prerequisite-patch-id: b9adc9622920a3e70168e672c2c92795b3e3a106 >> prerequisite-patch-id: 5030de433a01c2e99056cadb676a8e2ba35f055a > The patch looks correct, though will be better to have a smaller diff that doesn't require LLM for a review. -void drm_calc_timestamping_constants(struct drm_crtc *crtc, - const struct drm_display_mode *mode) +int drm_calc_timestamping_constants(struct drm_crtc *crtc, + const struct drm_display_mode *mode) { struct drm_device *dev = crtc->dev; unsigned int pipe = drm_crtc_index(crtc); struct drm_vblank_crtc *vblank = drm_crtc_vblank_crtc(crtc); - int linedur_ns = 0, framedur_ns = 0; + u64 linedur_ns = 0, framedur_ns = 0; int dotclock = mode->crtc_clock; if (!drm_dev_has_vblank(dev)) - return; + return 0; if (drm_WARN_ON(dev, pipe >= dev->num_crtcs)) - return; + return -EINVAL; /* Valid dotclock? */ if (dotclock > 0) { - int frame_size = mode->crtc_htotal * mode->crtc_vtotal; + u64 frame_size = (u64)mode->crtc_htotal * mode->crtc_vtotal; /* * Convert scanline length in pixels and video * dot clock to line duration and frame duration * in nanoseconds: */ linedur_ns = div_u64((u64) mode->crtc_htotal * 1000000, dotclock); - framedur_ns = div_u64((u64) frame_size * 1000000, dotclock); + framedur_ns = div_u64(frame_size * 1000000, dotclock); /* * Fields of interlaced scanout modes are only half a frame duration. */ if (mode->flags & DRM_MODE_FLAG_INTERLACE) framedur_ns /= 2; + + if (linedur_ns > INT_MAX || framedur_ns > INT_MAX) { + drm_err_once(dev, "crtc %u: Can't calculate constants, mode clock too small!\n", + crtc->base.id); + linedur_ns = 0; + framedur_ns = 0; + dotclock = 0; /* reuse the existing error-logging branch below */ + } } else { - drm_err(dev, "crtc %u: Can't calculate constants, dotclock = 0!\n", - crtc->base.id); + drm_err_once(dev, "crtc %u: Can't calculate constants, dotclock = %d!\n", + crtc->base.id, dotclock); } -- Best regards, Dmitry ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v4] drm/vblank: Don't arm vblank timer with invalid frame duration 2026-09-14 10:57 [PATCH v4] drm/vblank: Don't arm vblank timer with invalid frame duration Thomas Zimmermann 2026-09-14 11:11 ` sashiko-bot 2026-10-28 7:29 ` Thomas Zimmermann @ 2026-10-28 14:54 ` Ville Syrjälä 2026-10-29 11:35 ` Thomas Zimmermann 2 siblings, 1 reply; 14+ messages in thread From: Ville Syrjälä @ 2026-10-28 14:54 UTC (permalink / raw) To: Thomas Zimmermann Cc: me, louis.chauvet, maarten.lankhorst, mripard, airlied, simona, javierm, dmitry.osipenko, regressions, peter.m.arnesen, dri-devel, virtualization, sashiko-reviews On Mon, Sep 14, 2026 at 12:57:26PM +0200, Thomas Zimmermann wrote: > From: Roman Ilin <me@romanilin.is> > > When a CRTC's display mode carries a too small pixel clock, > drm_calc_timestamping_constants() computes a frame duration that > exceeds INT_MAX. drm_vblank_crtc.framedur_ns becomes negative. > drm_crtc_vblank_start_timer() then arms the vblank hrtimer with this > interval, after which vblank events are no longer delivered. Pending > page flips never complete and the display appears frozen. > > This could be triggered on virtio-gpu guests that have dynamic resolution > enabled: when the SPICE agent or the X server resizes the output, it > submits a mode whose pixel clock is off by a factor of 1000, e.g.: > > clock = 406 kHz, htotal = 3152, vtotal = 2148 How about actually "fixing" the clock in the driver? > > framedur_ns = 3152 * 2148 * 1000000 / 406 = 16675852216 ns (~16.7 s) > > 16675852216 does not fit into an int and wraps to roughly -504000000. > ns_to_ktime() then yields a negative interval and the timer stops working. > > Found by bisection, which pointed at commit a036f5fceedb ("drm/virtgpu: > Use vblank timer"). That commit merely made virtio-gpu use the vblank > timer and thereby exposed the pre-existing problem in the timer setup > added by commit 74afeb812850 ("drm/vblank: Add vblank timer"). > > To fix this, modify drm_calc_timestamping_constants() to use u64 for > calculations, check for INT_MAX overflows, and return an error code. > drm_crtc_vblank_start_timer() will then propagate the error, enabling > the driver to fall back to immediate vblank events. Valid modes are > unaffected, and the timer self-heals on the next mode with a sane clock. > > v4: > - avoid log spamming with drm_warn_once() > > Fixes: 74afeb812850 ("drm/vblank: Add vblank timer") > Suggested-by: Thomas Zimmermann <tzimmermann@suse.de> > Signed-off-by: Roman Ilin <me@romanilin.is> > --- > I took Roman's patch from [1] and replaced the error messages with > warn_once, as suggestde in my review. > > [1] https://lore.kernel.org/dri-devel/20260702181027.98526-1-me@romanilin.is/ > --- > drivers/gpu/drm/drm_vblank.c | 73 +++++++++++++++++++++++------------- > include/drm/drm_vblank.h | 4 +- > 2 files changed, 49 insertions(+), 28 deletions(-) > > diff --git a/drivers/gpu/drm/drm_vblank.c b/drivers/gpu/drm/drm_vblank.c > index f90fb2d13e42..72b543bca9e3 100644 > --- a/drivers/gpu/drm/drm_vblank.c > +++ b/drivers/gpu/drm/drm_vblank.c > @@ -631,45 +631,54 @@ EXPORT_SYMBOL(drm_crtc_vblank_waitqueue); > * drm_crtc_vblank_helper_get_vblank_timestamp(). They are derived from > * CRTC's true scanout timing, so they take things like panel scaling or > * other adjustments into account. > + * > + * Returns: > + * 0 on success, or a negative errno code otherwise. > */ > -void drm_calc_timestamping_constants(struct drm_crtc *crtc, > - const struct drm_display_mode *mode) > +int drm_calc_timestamping_constants(struct drm_crtc *crtc, > + const struct drm_display_mode *mode) > { > struct drm_device *dev = crtc->dev; > unsigned int pipe = drm_crtc_index(crtc); > struct drm_vblank_crtc *vblank = drm_crtc_vblank_crtc(crtc); > - int linedur_ns = 0, framedur_ns = 0; > + u64 linedur_ns, framedur_ns; > int dotclock = mode->crtc_clock; > + unsigned int frame_size; > > if (!drm_dev_has_vblank(dev)) > - return; > + return 0; > > if (drm_WARN_ON(dev, pipe >= dev->num_crtcs)) > - return; > + return -EINVAL; > > - /* Valid dotclock? */ > - if (dotclock > 0) { > - int frame_size = mode->crtc_htotal * mode->crtc_vtotal; > + if (dotclock <= 0) { > + drm_err_once(dev, "crtc %u: Can't calculate constants, dotclock = %d!\n", > + crtc->base.id, dotclock); > + goto error; > + } > > - /* > - * Convert scanline length in pixels and video > - * dot clock to line duration and frame duration > - * in nanoseconds: > - */ > - linedur_ns = div_u64((u64) mode->crtc_htotal * 1000000, dotclock); > - framedur_ns = div_u64((u64) frame_size * 1000000, dotclock); > + frame_size = (unsigned int)mode->crtc_htotal * (unsigned int)mode->crtc_vtotal; > > - /* > - * Fields of interlaced scanout modes are only half a frame duration. > - */ > - if (mode->flags & DRM_MODE_FLAG_INTERLACE) > - framedur_ns /= 2; > - } else { > - drm_err(dev, "crtc %u: Can't calculate constants, dotclock = 0!\n", > - crtc->base.id); > + /* > + * Convert scanline length in pixels and video dot clock to line duration > + * and frame duration in nanoseconds. > + */ > + linedur_ns = div_u64((u64)mode->crtc_htotal * 1000000, dotclock); > + framedur_ns = div_u64((u64)frame_size * 1000000, dotclock); > + > + /* > + * Fields of interlaced scanout modes are only half a frame duration. > + */ > + if (mode->flags & DRM_MODE_FLAG_INTERLACE) > + framedur_ns /= 2; > + > + if (linedur_ns > INT_MAX || framedur_ns > INT_MAX) { > + drm_err_once(dev, "crtc %u: Can't calculate constants, mode clock too small!\n", > + crtc->base.id); > + goto error; > } > > - vblank->linedur_ns = linedur_ns; > + vblank->linedur_ns = linedur_ns; > vblank->framedur_ns = framedur_ns; > drm_mode_copy(&vblank->hwmode, mode); > > @@ -678,7 +687,16 @@ void drm_calc_timestamping_constants(struct drm_crtc *crtc, > crtc->base.id, mode->crtc_htotal, > mode->crtc_vtotal, mode->crtc_vdisplay); > drm_dbg_core(dev, "crtc %u: clock %d kHz framedur %d linedur %d\n", > - crtc->base.id, dotclock, framedur_ns, linedur_ns); > + crtc->base.id, dotclock, > + vblank->framedur_ns, vblank->linedur_ns); > + > + return 0; > + > +error: > + vblank->linedur_ns = 0; > + vblank->framedur_ns = 0; > + drm_mode_copy(&vblank->hwmode, mode); > + return -EINVAL; > } > EXPORT_SYMBOL(drm_calc_timestamping_constants); > > @@ -2221,6 +2239,7 @@ 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; > + int ret; > > if (!vtimer->crtc) { > /* > @@ -2239,7 +2258,9 @@ int drm_crtc_vblank_start_timer(struct drm_crtc *crtc) > hrtimer_try_to_cancel(&vtimer->timer); > } > > - drm_calc_timestamping_constants(crtc, &crtc->mode); > + ret = drm_calc_timestamping_constants(crtc, &crtc->mode); > + if (ret) > + return ret; > > spin_lock_irqsave(&vtimer->interval_lock, flags); > vtimer->interval = ns_to_ktime(vblank->framedur_ns); > diff --git a/include/drm/drm_vblank.h b/include/drm/drm_vblank.h > index 2fcef9c0f5b1..d99772dfa034 100644 > --- a/include/drm/drm_vblank.h > +++ b/include/drm/drm_vblank.h > @@ -311,8 +311,8 @@ void drm_crtc_vblank_on(struct drm_crtc *crtc); > u64 drm_crtc_accurate_vblank_count(struct drm_crtc *crtc); > void drm_crtc_vblank_restore(struct drm_crtc *crtc); > > -void drm_calc_timestamping_constants(struct drm_crtc *crtc, > - const struct drm_display_mode *mode); > +int drm_calc_timestamping_constants(struct drm_crtc *crtc, > + const struct drm_display_mode *mode); > wait_queue_head_t *drm_crtc_vblank_waitqueue(struct drm_crtc *crtc); > void drm_crtc_set_max_vblank_count(struct drm_crtc *crtc, > u32 max_vblank_count); > > base-commit: 06576c2b43750d990297b8ff1989b94ac72e855f > prerequisite-patch-id: 6729bb10d5e338678e78ccda2dd24f232a13cd22 > prerequisite-patch-id: 9d94d20bd45967493b4dc339b58082e1c8da0f4f > prerequisite-patch-id: 6ac8d22df34db9e7d467b1ab95ad30f94841d5db > prerequisite-patch-id: f548dbde3f944ca3415c808be628719acebe898a > prerequisite-patch-id: 2ae91ee8e2bbcd33b8325be046e7214185aba6aa > prerequisite-patch-id: f97bbde08a9996d7c62ca3e3ebfcd387e50bc5c3 > prerequisite-patch-id: ecc3c825277bc9978c6979455517249a074c0cb1 > prerequisite-patch-id: 0449925cca36664b566f614bfb7d573a11f0575f > prerequisite-patch-id: 9fb2c2787244e9621847af09bea03e575e6276c7 > prerequisite-patch-id: eb4384d581805e1178d22e7df45f134223c38305 > prerequisite-patch-id: 1a7a529e4b2864abe8071aa44598d28ce72483f4 > prerequisite-patch-id: 844a85f4c2fe2f72209b82d46614004c3cfe4123 > prerequisite-patch-id: 13f7cb22114c648d35f8c9fa74e9165656ce31eb > prerequisite-patch-id: 20ee46bd6fdd91e39f71d22f52ef6682a4e41512 > prerequisite-patch-id: 1d26c0ff91796ff1e62d3831d00bee18723788ef > prerequisite-patch-id: 007f6ea0d09bef54b689bae0eb5abeb497bdfaad > prerequisite-patch-id: 2d89c1507c909520adf9bfb1a137d458a819ae8f > prerequisite-patch-id: d90dc4134468870ab8ccbd71547c9552dff74fbd > prerequisite-patch-id: c67e5d886a47b7d0266d81100837557fda34cb24 > prerequisite-patch-id: a5a973e527c88a5b47053d7a72aefe0b550197cb > prerequisite-patch-id: b9adc9622920a3e70168e672c2c92795b3e3a106 > prerequisite-patch-id: 5030de433a01c2e99056cadb676a8e2ba35f055a > -- > 2.55.0 -- Ville Syrjälä Intel ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v4] drm/vblank: Don't arm vblank timer with invalid frame duration 2026-10-28 14:54 ` Ville Syrjälä @ 2026-10-29 11:35 ` Thomas Zimmermann 2026-10-30 12:07 ` Ville Syrjälä 0 siblings, 1 reply; 14+ messages in thread From: Thomas Zimmermann @ 2026-10-29 11:35 UTC (permalink / raw) To: Ville Syrjälä Cc: me, louis.chauvet, maarten.lankhorst, mripard, airlied, simona, javierm, dmitry.osipenko, regressions, peter.m.arnesen, dri-devel, virtualization, sashiko-reviews Hi Am 28.10.26 um 15:54 schrieb Ville Syrjälä: > On Mon, Sep 14, 2026 at 12:57:26PM +0200, Thomas Zimmermann wrote: >> From: Roman Ilin <me@romanilin.is> >> >> When a CRTC's display mode carries a too small pixel clock, >> drm_calc_timestamping_constants() computes a frame duration that >> exceeds INT_MAX. drm_vblank_crtc.framedur_ns becomes negative. >> drm_crtc_vblank_start_timer() then arms the vblank hrtimer with this >> interval, after which vblank events are no longer delivered. Pending >> page flips never complete and the display appears frozen. >> >> This could be triggered on virtio-gpu guests that have dynamic resolution >> enabled: when the SPICE agent or the X server resizes the output, it >> submits a mode whose pixel clock is off by a factor of 1000, e.g.: >> >> clock = 406 kHz, htotal = 3152, vtotal = 2148 > How about actually "fixing" the clock in the driver? It's not a broken driver, but user space. There is a bug report open for spice to replace the incorrect units with correct ones. [1] The fix here is supposed to resolve the kernel problem this triggers. [1] https://gitlab.freedesktop.org/spice/linux/vd_agent/-/work_items/52 Best regards Thomas > >> framedur_ns = 3152 * 2148 * 1000000 / 406 = 16675852216 ns (~16.7 s) >> >> 16675852216 does not fit into an int and wraps to roughly -504000000. >> ns_to_ktime() then yields a negative interval and the timer stops working. >> >> Found by bisection, which pointed at commit a036f5fceedb ("drm/virtgpu: >> Use vblank timer"). That commit merely made virtio-gpu use the vblank >> timer and thereby exposed the pre-existing problem in the timer setup >> added by commit 74afeb812850 ("drm/vblank: Add vblank timer"). >> >> To fix this, modify drm_calc_timestamping_constants() to use u64 for >> calculations, check for INT_MAX overflows, and return an error code. >> drm_crtc_vblank_start_timer() will then propagate the error, enabling >> the driver to fall back to immediate vblank events. Valid modes are >> unaffected, and the timer self-heals on the next mode with a sane clock. >> >> v4: >> - avoid log spamming with drm_warn_once() >> >> Fixes: 74afeb812850 ("drm/vblank: Add vblank timer") >> Suggested-by: Thomas Zimmermann <tzimmermann@suse.de> >> Signed-off-by: Roman Ilin <me@romanilin.is> >> --- >> I took Roman's patch from [1] and replaced the error messages with >> warn_once, as suggestde in my review. >> >> [1] https://lore.kernel.org/dri-devel/20260702181027.98526-1-me@romanilin.is/ >> --- >> drivers/gpu/drm/drm_vblank.c | 73 +++++++++++++++++++++++------------- >> include/drm/drm_vblank.h | 4 +- >> 2 files changed, 49 insertions(+), 28 deletions(-) >> >> diff --git a/drivers/gpu/drm/drm_vblank.c b/drivers/gpu/drm/drm_vblank.c >> index f90fb2d13e42..72b543bca9e3 100644 >> --- a/drivers/gpu/drm/drm_vblank.c >> +++ b/drivers/gpu/drm/drm_vblank.c >> @@ -631,45 +631,54 @@ EXPORT_SYMBOL(drm_crtc_vblank_waitqueue); >> * drm_crtc_vblank_helper_get_vblank_timestamp(). They are derived from >> * CRTC's true scanout timing, so they take things like panel scaling or >> * other adjustments into account. >> + * >> + * Returns: >> + * 0 on success, or a negative errno code otherwise. >> */ >> -void drm_calc_timestamping_constants(struct drm_crtc *crtc, >> - const struct drm_display_mode *mode) >> +int drm_calc_timestamping_constants(struct drm_crtc *crtc, >> + const struct drm_display_mode *mode) >> { >> struct drm_device *dev = crtc->dev; >> unsigned int pipe = drm_crtc_index(crtc); >> struct drm_vblank_crtc *vblank = drm_crtc_vblank_crtc(crtc); >> - int linedur_ns = 0, framedur_ns = 0; >> + u64 linedur_ns, framedur_ns; >> int dotclock = mode->crtc_clock; >> + unsigned int frame_size; >> >> if (!drm_dev_has_vblank(dev)) >> - return; >> + return 0; >> >> if (drm_WARN_ON(dev, pipe >= dev->num_crtcs)) >> - return; >> + return -EINVAL; >> >> - /* Valid dotclock? */ >> - if (dotclock > 0) { >> - int frame_size = mode->crtc_htotal * mode->crtc_vtotal; >> + if (dotclock <= 0) { >> + drm_err_once(dev, "crtc %u: Can't calculate constants, dotclock = %d!\n", >> + crtc->base.id, dotclock); >> + goto error; >> + } >> >> - /* >> - * Convert scanline length in pixels and video >> - * dot clock to line duration and frame duration >> - * in nanoseconds: >> - */ >> - linedur_ns = div_u64((u64) mode->crtc_htotal * 1000000, dotclock); >> - framedur_ns = div_u64((u64) frame_size * 1000000, dotclock); >> + frame_size = (unsigned int)mode->crtc_htotal * (unsigned int)mode->crtc_vtotal; >> >> - /* >> - * Fields of interlaced scanout modes are only half a frame duration. >> - */ >> - if (mode->flags & DRM_MODE_FLAG_INTERLACE) >> - framedur_ns /= 2; >> - } else { >> - drm_err(dev, "crtc %u: Can't calculate constants, dotclock = 0!\n", >> - crtc->base.id); >> + /* >> + * Convert scanline length in pixels and video dot clock to line duration >> + * and frame duration in nanoseconds. >> + */ >> + linedur_ns = div_u64((u64)mode->crtc_htotal * 1000000, dotclock); >> + framedur_ns = div_u64((u64)frame_size * 1000000, dotclock); >> + >> + /* >> + * Fields of interlaced scanout modes are only half a frame duration. >> + */ >> + if (mode->flags & DRM_MODE_FLAG_INTERLACE) >> + framedur_ns /= 2; >> + >> + if (linedur_ns > INT_MAX || framedur_ns > INT_MAX) { >> + drm_err_once(dev, "crtc %u: Can't calculate constants, mode clock too small!\n", >> + crtc->base.id); >> + goto error; >> } >> >> - vblank->linedur_ns = linedur_ns; >> + vblank->linedur_ns = linedur_ns; >> vblank->framedur_ns = framedur_ns; >> drm_mode_copy(&vblank->hwmode, mode); >> >> @@ -678,7 +687,16 @@ void drm_calc_timestamping_constants(struct drm_crtc *crtc, >> crtc->base.id, mode->crtc_htotal, >> mode->crtc_vtotal, mode->crtc_vdisplay); >> drm_dbg_core(dev, "crtc %u: clock %d kHz framedur %d linedur %d\n", >> - crtc->base.id, dotclock, framedur_ns, linedur_ns); >> + crtc->base.id, dotclock, >> + vblank->framedur_ns, vblank->linedur_ns); >> + >> + return 0; >> + >> +error: >> + vblank->linedur_ns = 0; >> + vblank->framedur_ns = 0; >> + drm_mode_copy(&vblank->hwmode, mode); >> + return -EINVAL; >> } >> EXPORT_SYMBOL(drm_calc_timestamping_constants); >> >> @@ -2221,6 +2239,7 @@ 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; >> + int ret; >> >> if (!vtimer->crtc) { >> /* >> @@ -2239,7 +2258,9 @@ int drm_crtc_vblank_start_timer(struct drm_crtc *crtc) >> hrtimer_try_to_cancel(&vtimer->timer); >> } >> >> - drm_calc_timestamping_constants(crtc, &crtc->mode); >> + ret = drm_calc_timestamping_constants(crtc, &crtc->mode); >> + if (ret) >> + return ret; >> >> spin_lock_irqsave(&vtimer->interval_lock, flags); >> vtimer->interval = ns_to_ktime(vblank->framedur_ns); >> diff --git a/include/drm/drm_vblank.h b/include/drm/drm_vblank.h >> index 2fcef9c0f5b1..d99772dfa034 100644 >> --- a/include/drm/drm_vblank.h >> +++ b/include/drm/drm_vblank.h >> @@ -311,8 +311,8 @@ void drm_crtc_vblank_on(struct drm_crtc *crtc); >> u64 drm_crtc_accurate_vblank_count(struct drm_crtc *crtc); >> void drm_crtc_vblank_restore(struct drm_crtc *crtc); >> >> -void drm_calc_timestamping_constants(struct drm_crtc *crtc, >> - const struct drm_display_mode *mode); >> +int drm_calc_timestamping_constants(struct drm_crtc *crtc, >> + const struct drm_display_mode *mode); >> wait_queue_head_t *drm_crtc_vblank_waitqueue(struct drm_crtc *crtc); >> void drm_crtc_set_max_vblank_count(struct drm_crtc *crtc, >> u32 max_vblank_count); >> >> base-commit: 06576c2b43750d990297b8ff1989b94ac72e855f >> prerequisite-patch-id: 6729bb10d5e338678e78ccda2dd24f232a13cd22 >> prerequisite-patch-id: 9d94d20bd45967493b4dc339b58082e1c8da0f4f >> prerequisite-patch-id: 6ac8d22df34db9e7d467b1ab95ad30f94841d5db >> prerequisite-patch-id: f548dbde3f944ca3415c808be628719acebe898a >> prerequisite-patch-id: 2ae91ee8e2bbcd33b8325be046e7214185aba6aa >> prerequisite-patch-id: f97bbde08a9996d7c62ca3e3ebfcd387e50bc5c3 >> prerequisite-patch-id: ecc3c825277bc9978c6979455517249a074c0cb1 >> prerequisite-patch-id: 0449925cca36664b566f614bfb7d573a11f0575f >> prerequisite-patch-id: 9fb2c2787244e9621847af09bea03e575e6276c7 >> prerequisite-patch-id: eb4384d581805e1178d22e7df45f134223c38305 >> prerequisite-patch-id: 1a7a529e4b2864abe8071aa44598d28ce72483f4 >> prerequisite-patch-id: 844a85f4c2fe2f72209b82d46614004c3cfe4123 >> prerequisite-patch-id: 13f7cb22114c648d35f8c9fa74e9165656ce31eb >> prerequisite-patch-id: 20ee46bd6fdd91e39f71d22f52ef6682a4e41512 >> prerequisite-patch-id: 1d26c0ff91796ff1e62d3831d00bee18723788ef >> prerequisite-patch-id: 007f6ea0d09bef54b689bae0eb5abeb497bdfaad >> prerequisite-patch-id: 2d89c1507c909520adf9bfb1a137d458a819ae8f >> prerequisite-patch-id: d90dc4134468870ab8ccbd71547c9552dff74fbd >> prerequisite-patch-id: c67e5d886a47b7d0266d81100837557fda34cb24 >> prerequisite-patch-id: a5a973e527c88a5b47053d7a72aefe0b550197cb >> prerequisite-patch-id: b9adc9622920a3e70168e672c2c92795b3e3a106 >> prerequisite-patch-id: 5030de433a01c2e99056cadb676a8e2ba35f055a >> -- >> 2.55.0 -- -- Thomas Zimmermann Graphics Driver Developer SUSE Software Solutions Germany GmbH Frankenstr. 146, 90461 Nürnberg, Germany, www.suse.com GF: Stefan Gaiser, Jochen Jaser, Abhinav Puri, (HRB 36809, AG Nürnberg) ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v4] drm/vblank: Don't arm vblank timer with invalid frame duration 2026-10-29 11:35 ` Thomas Zimmermann @ 2026-10-30 12:07 ` Ville Syrjälä 2026-10-30 13:04 ` Thomas Zimmermann 0 siblings, 1 reply; 14+ messages in thread From: Ville Syrjälä @ 2026-10-30 12:07 UTC (permalink / raw) To: Thomas Zimmermann Cc: me, louis.chauvet, maarten.lankhorst, mripard, airlied, simona, javierm, dmitry.osipenko, regressions, peter.m.arnesen, dri-devel, virtualization, sashiko-reviews On Thu, Oct 29, 2026 at 12:35:52PM +0100, Thomas Zimmermann wrote: > Hi > > Am 28.10.26 um 15:54 schrieb Ville Syrjälä: > > On Mon, Sep 14, 2026 at 12:57:26PM +0200, Thomas Zimmermann wrote: > >> From: Roman Ilin <me@romanilin.is> > >> > >> When a CRTC's display mode carries a too small pixel clock, > >> drm_calc_timestamping_constants() computes a frame duration that > >> exceeds INT_MAX. drm_vblank_crtc.framedur_ns becomes negative. > >> drm_crtc_vblank_start_timer() then arms the vblank hrtimer with this > >> interval, after which vblank events are no longer delivered. Pending > >> page flips never complete and the display appears frozen. > >> > >> This could be triggered on virtio-gpu guests that have dynamic resolution > >> enabled: when the SPICE agent or the X server resizes the output, it > >> submits a mode whose pixel clock is off by a factor of 1000, e.g.: > >> > >> clock = 406 kHz, htotal = 3152, vtotal = 2148 > > How about actually "fixing" the clock in the driver? > > It's not a broken driver, but user space. There is a bug report open for > spice to replace the incorrect units with correct ones. [1] The fix here > is supposed to resolve the kernel problem this triggers. Life becomes much simpler if you don't let garbage userspace input past some initial sanity checks. So technically the driver should just reject these modes. But in this case since you need to keep the broken userspace working the driver should just fix it up, and do so early so you don't have to deal with it anywhere else. > > [1] https://gitlab.freedesktop.org/spice/linux/vd_agent/-/work_items/52 > > Best regards > Thomas > > > > > >> framedur_ns = 3152 * 2148 * 1000000 / 406 = 16675852216 ns (~16.7 s) > >> > >> 16675852216 does not fit into an int and wraps to roughly -504000000. > >> ns_to_ktime() then yields a negative interval and the timer stops working. > >> > >> Found by bisection, which pointed at commit a036f5fceedb ("drm/virtgpu: > >> Use vblank timer"). That commit merely made virtio-gpu use the vblank > >> timer and thereby exposed the pre-existing problem in the timer setup > >> added by commit 74afeb812850 ("drm/vblank: Add vblank timer"). > >> > >> To fix this, modify drm_calc_timestamping_constants() to use u64 for > >> calculations, check for INT_MAX overflows, and return an error code. > >> drm_crtc_vblank_start_timer() will then propagate the error, enabling > >> the driver to fall back to immediate vblank events. Valid modes are > >> unaffected, and the timer self-heals on the next mode with a sane clock. > >> > >> v4: > >> - avoid log spamming with drm_warn_once() > >> > >> Fixes: 74afeb812850 ("drm/vblank: Add vblank timer") > >> Suggested-by: Thomas Zimmermann <tzimmermann@suse.de> > >> Signed-off-by: Roman Ilin <me@romanilin.is> > >> --- > >> I took Roman's patch from [1] and replaced the error messages with > >> warn_once, as suggestde in my review. > >> > >> [1] https://lore.kernel.org/dri-devel/20260702181027.98526-1-me@romanilin.is/ > >> --- > >> drivers/gpu/drm/drm_vblank.c | 73 +++++++++++++++++++++++------------- > >> include/drm/drm_vblank.h | 4 +- > >> 2 files changed, 49 insertions(+), 28 deletions(-) > >> > >> diff --git a/drivers/gpu/drm/drm_vblank.c b/drivers/gpu/drm/drm_vblank.c > >> index f90fb2d13e42..72b543bca9e3 100644 > >> --- a/drivers/gpu/drm/drm_vblank.c > >> +++ b/drivers/gpu/drm/drm_vblank.c > >> @@ -631,45 +631,54 @@ EXPORT_SYMBOL(drm_crtc_vblank_waitqueue); > >> * drm_crtc_vblank_helper_get_vblank_timestamp(). They are derived from > >> * CRTC's true scanout timing, so they take things like panel scaling or > >> * other adjustments into account. > >> + * > >> + * Returns: > >> + * 0 on success, or a negative errno code otherwise. > >> */ > >> -void drm_calc_timestamping_constants(struct drm_crtc *crtc, > >> - const struct drm_display_mode *mode) > >> +int drm_calc_timestamping_constants(struct drm_crtc *crtc, > >> + const struct drm_display_mode *mode) > >> { > >> struct drm_device *dev = crtc->dev; > >> unsigned int pipe = drm_crtc_index(crtc); > >> struct drm_vblank_crtc *vblank = drm_crtc_vblank_crtc(crtc); > >> - int linedur_ns = 0, framedur_ns = 0; > >> + u64 linedur_ns, framedur_ns; > >> int dotclock = mode->crtc_clock; > >> + unsigned int frame_size; > >> > >> if (!drm_dev_has_vblank(dev)) > >> - return; > >> + return 0; > >> > >> if (drm_WARN_ON(dev, pipe >= dev->num_crtcs)) > >> - return; > >> + return -EINVAL; > >> > >> - /* Valid dotclock? */ > >> - if (dotclock > 0) { > >> - int frame_size = mode->crtc_htotal * mode->crtc_vtotal; > >> + if (dotclock <= 0) { > >> + drm_err_once(dev, "crtc %u: Can't calculate constants, dotclock = %d!\n", > >> + crtc->base.id, dotclock); > >> + goto error; > >> + } > >> > >> - /* > >> - * Convert scanline length in pixels and video > >> - * dot clock to line duration and frame duration > >> - * in nanoseconds: > >> - */ > >> - linedur_ns = div_u64((u64) mode->crtc_htotal * 1000000, dotclock); > >> - framedur_ns = div_u64((u64) frame_size * 1000000, dotclock); > >> + frame_size = (unsigned int)mode->crtc_htotal * (unsigned int)mode->crtc_vtotal; > >> > >> - /* > >> - * Fields of interlaced scanout modes are only half a frame duration. > >> - */ > >> - if (mode->flags & DRM_MODE_FLAG_INTERLACE) > >> - framedur_ns /= 2; > >> - } else { > >> - drm_err(dev, "crtc %u: Can't calculate constants, dotclock = 0!\n", > >> - crtc->base.id); > >> + /* > >> + * Convert scanline length in pixels and video dot clock to line duration > >> + * and frame duration in nanoseconds. > >> + */ > >> + linedur_ns = div_u64((u64)mode->crtc_htotal * 1000000, dotclock); > >> + framedur_ns = div_u64((u64)frame_size * 1000000, dotclock); > >> + > >> + /* > >> + * Fields of interlaced scanout modes are only half a frame duration. > >> + */ > >> + if (mode->flags & DRM_MODE_FLAG_INTERLACE) > >> + framedur_ns /= 2; > >> + > >> + if (linedur_ns > INT_MAX || framedur_ns > INT_MAX) { > >> + drm_err_once(dev, "crtc %u: Can't calculate constants, mode clock too small!\n", > >> + crtc->base.id); > >> + goto error; > >> } > >> > >> - vblank->linedur_ns = linedur_ns; > >> + vblank->linedur_ns = linedur_ns; > >> vblank->framedur_ns = framedur_ns; > >> drm_mode_copy(&vblank->hwmode, mode); > >> > >> @@ -678,7 +687,16 @@ void drm_calc_timestamping_constants(struct drm_crtc *crtc, > >> crtc->base.id, mode->crtc_htotal, > >> mode->crtc_vtotal, mode->crtc_vdisplay); > >> drm_dbg_core(dev, "crtc %u: clock %d kHz framedur %d linedur %d\n", > >> - crtc->base.id, dotclock, framedur_ns, linedur_ns); > >> + crtc->base.id, dotclock, > >> + vblank->framedur_ns, vblank->linedur_ns); > >> + > >> + return 0; > >> + > >> +error: > >> + vblank->linedur_ns = 0; > >> + vblank->framedur_ns = 0; > >> + drm_mode_copy(&vblank->hwmode, mode); > >> + return -EINVAL; > >> } > >> EXPORT_SYMBOL(drm_calc_timestamping_constants); > >> > >> @@ -2221,6 +2239,7 @@ 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; > >> + int ret; > >> > >> if (!vtimer->crtc) { > >> /* > >> @@ -2239,7 +2258,9 @@ int drm_crtc_vblank_start_timer(struct drm_crtc *crtc) > >> hrtimer_try_to_cancel(&vtimer->timer); > >> } > >> > >> - drm_calc_timestamping_constants(crtc, &crtc->mode); > >> + ret = drm_calc_timestamping_constants(crtc, &crtc->mode); > >> + if (ret) > >> + return ret; > >> > >> spin_lock_irqsave(&vtimer->interval_lock, flags); > >> vtimer->interval = ns_to_ktime(vblank->framedur_ns); > >> diff --git a/include/drm/drm_vblank.h b/include/drm/drm_vblank.h > >> index 2fcef9c0f5b1..d99772dfa034 100644 > >> --- a/include/drm/drm_vblank.h > >> +++ b/include/drm/drm_vblank.h > >> @@ -311,8 +311,8 @@ void drm_crtc_vblank_on(struct drm_crtc *crtc); > >> u64 drm_crtc_accurate_vblank_count(struct drm_crtc *crtc); > >> void drm_crtc_vblank_restore(struct drm_crtc *crtc); > >> > >> -void drm_calc_timestamping_constants(struct drm_crtc *crtc, > >> - const struct drm_display_mode *mode); > >> +int drm_calc_timestamping_constants(struct drm_crtc *crtc, > >> + const struct drm_display_mode *mode); > >> wait_queue_head_t *drm_crtc_vblank_waitqueue(struct drm_crtc *crtc); > >> void drm_crtc_set_max_vblank_count(struct drm_crtc *crtc, > >> u32 max_vblank_count); > >> > >> base-commit: 06576c2b43750d990297b8ff1989b94ac72e855f > >> prerequisite-patch-id: 6729bb10d5e338678e78ccda2dd24f232a13cd22 > >> prerequisite-patch-id: 9d94d20bd45967493b4dc339b58082e1c8da0f4f > >> prerequisite-patch-id: 6ac8d22df34db9e7d467b1ab95ad30f94841d5db > >> prerequisite-patch-id: f548dbde3f944ca3415c808be628719acebe898a > >> prerequisite-patch-id: 2ae91ee8e2bbcd33b8325be046e7214185aba6aa > >> prerequisite-patch-id: f97bbde08a9996d7c62ca3e3ebfcd387e50bc5c3 > >> prerequisite-patch-id: ecc3c825277bc9978c6979455517249a074c0cb1 > >> prerequisite-patch-id: 0449925cca36664b566f614bfb7d573a11f0575f > >> prerequisite-patch-id: 9fb2c2787244e9621847af09bea03e575e6276c7 > >> prerequisite-patch-id: eb4384d581805e1178d22e7df45f134223c38305 > >> prerequisite-patch-id: 1a7a529e4b2864abe8071aa44598d28ce72483f4 > >> prerequisite-patch-id: 844a85f4c2fe2f72209b82d46614004c3cfe4123 > >> prerequisite-patch-id: 13f7cb22114c648d35f8c9fa74e9165656ce31eb > >> prerequisite-patch-id: 20ee46bd6fdd91e39f71d22f52ef6682a4e41512 > >> prerequisite-patch-id: 1d26c0ff91796ff1e62d3831d00bee18723788ef > >> prerequisite-patch-id: 007f6ea0d09bef54b689bae0eb5abeb497bdfaad > >> prerequisite-patch-id: 2d89c1507c909520adf9bfb1a137d458a819ae8f > >> prerequisite-patch-id: d90dc4134468870ab8ccbd71547c9552dff74fbd > >> prerequisite-patch-id: c67e5d886a47b7d0266d81100837557fda34cb24 > >> prerequisite-patch-id: a5a973e527c88a5b47053d7a72aefe0b550197cb > >> prerequisite-patch-id: b9adc9622920a3e70168e672c2c92795b3e3a106 > >> prerequisite-patch-id: 5030de433a01c2e99056cadb676a8e2ba35f055a > >> -- > >> 2.55.0 > > -- > -- > Thomas Zimmermann > Graphics Driver Developer > SUSE Software Solutions Germany GmbH > Frankenstr. 146, 90461 Nürnberg, Germany, www.suse.com > GF: Stefan Gaiser, Jochen Jaser, Abhinav Puri, (HRB 36809, AG Nürnberg) > -- Ville Syrjälä Intel ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v4] drm/vblank: Don't arm vblank timer with invalid frame duration 2026-10-30 12:07 ` Ville Syrjälä @ 2026-10-30 13:04 ` Thomas Zimmermann 2026-10-30 13:48 ` Ville Syrjälä 0 siblings, 1 reply; 14+ messages in thread From: Thomas Zimmermann @ 2026-10-30 13:04 UTC (permalink / raw) To: Ville Syrjälä Cc: me, louis.chauvet, maarten.lankhorst, mripard, airlied, simona, javierm, dmitry.osipenko, regressions, peter.m.arnesen, dri-devel, virtualization, sashiko-reviews Hi Am 30.10.26 um 13:07 schrieb Ville Syrjälä: > On Thu, Oct 29, 2026 at 12:35:52PM +0100, Thomas Zimmermann wrote: >> Hi >> >> Am 28.10.26 um 15:54 schrieb Ville Syrjälä: >>> On Mon, Sep 14, 2026 at 12:57:26PM +0200, Thomas Zimmermann wrote: >>>> From: Roman Ilin <me@romanilin.is> >>>> >>>> When a CRTC's display mode carries a too small pixel clock, >>>> drm_calc_timestamping_constants() computes a frame duration that >>>> exceeds INT_MAX. drm_vblank_crtc.framedur_ns becomes negative. >>>> drm_crtc_vblank_start_timer() then arms the vblank hrtimer with this >>>> interval, after which vblank events are no longer delivered. Pending >>>> page flips never complete and the display appears frozen. >>>> >>>> This could be triggered on virtio-gpu guests that have dynamic resolution >>>> enabled: when the SPICE agent or the X server resizes the output, it >>>> submits a mode whose pixel clock is off by a factor of 1000, e.g.: >>>> >>>> clock = 406 kHz, htotal = 3152, vtotal = 2148 >>> How about actually "fixing" the clock in the driver? >> It's not a broken driver, but user space. There is a bug report open for >> spice to replace the incorrect units with correct ones. [1] The fix here >> is supposed to resolve the kernel problem this triggers. > Life becomes much simpler if you don't let garbage userspace input > past some initial sanity checks. So technically the driver should > just reject these modes. But in this case since you need to keep > the broken userspace working the driver should just fix it up, > and do so early so you don't have to deal with it anywhere else. Not that I disagree, with the statement in general. But doing this here seems like whack-a-mole if we leave it to individual drivers. And the userspace is already dis-functional, no need to keep supporting it, I think. And how would drivers know that the values are garbage before processing them? Even if a driver could program them to hardware, the vblank helpers could still not process them correctly. Thus leaving the userspace dis-functional. I see that we might want a common test function drm_vblank_check_display_mode() and put it in some central place in the atomic_check pipeline. At least we'd separate the testing from the calculation. Best regards Thomas > >> [1] https://gitlab.freedesktop.org/spice/linux/vd_agent/-/work_items/52 >> >> Best regards >> Thomas >> >> >>>> framedur_ns = 3152 * 2148 * 1000000 / 406 = 16675852216 ns (~16.7 s) >>>> >>>> 16675852216 does not fit into an int and wraps to roughly -504000000. >>>> ns_to_ktime() then yields a negative interval and the timer stops working. >>>> >>>> Found by bisection, which pointed at commit a036f5fceedb ("drm/virtgpu: >>>> Use vblank timer"). That commit merely made virtio-gpu use the vblank >>>> timer and thereby exposed the pre-existing problem in the timer setup >>>> added by commit 74afeb812850 ("drm/vblank: Add vblank timer"). >>>> >>>> To fix this, modify drm_calc_timestamping_constants() to use u64 for >>>> calculations, check for INT_MAX overflows, and return an error code. >>>> drm_crtc_vblank_start_timer() will then propagate the error, enabling >>>> the driver to fall back to immediate vblank events. Valid modes are >>>> unaffected, and the timer self-heals on the next mode with a sane clock. >>>> >>>> v4: >>>> - avoid log spamming with drm_warn_once() >>>> >>>> Fixes: 74afeb812850 ("drm/vblank: Add vblank timer") >>>> Suggested-by: Thomas Zimmermann <tzimmermann@suse.de> >>>> Signed-off-by: Roman Ilin <me@romanilin.is> >>>> --- >>>> I took Roman's patch from [1] and replaced the error messages with >>>> warn_once, as suggestde in my review. >>>> >>>> [1] https://lore.kernel.org/dri-devel/20260702181027.98526-1-me@romanilin.is/ >>>> --- >>>> drivers/gpu/drm/drm_vblank.c | 73 +++++++++++++++++++++++------------- >>>> include/drm/drm_vblank.h | 4 +- >>>> 2 files changed, 49 insertions(+), 28 deletions(-) >>>> >>>> diff --git a/drivers/gpu/drm/drm_vblank.c b/drivers/gpu/drm/drm_vblank.c >>>> index f90fb2d13e42..72b543bca9e3 100644 >>>> --- a/drivers/gpu/drm/drm_vblank.c >>>> +++ b/drivers/gpu/drm/drm_vblank.c >>>> @@ -631,45 +631,54 @@ EXPORT_SYMBOL(drm_crtc_vblank_waitqueue); >>>> * drm_crtc_vblank_helper_get_vblank_timestamp(). They are derived from >>>> * CRTC's true scanout timing, so they take things like panel scaling or >>>> * other adjustments into account. >>>> + * >>>> + * Returns: >>>> + * 0 on success, or a negative errno code otherwise. >>>> */ >>>> -void drm_calc_timestamping_constants(struct drm_crtc *crtc, >>>> - const struct drm_display_mode *mode) >>>> +int drm_calc_timestamping_constants(struct drm_crtc *crtc, >>>> + const struct drm_display_mode *mode) >>>> { >>>> struct drm_device *dev = crtc->dev; >>>> unsigned int pipe = drm_crtc_index(crtc); >>>> struct drm_vblank_crtc *vblank = drm_crtc_vblank_crtc(crtc); >>>> - int linedur_ns = 0, framedur_ns = 0; >>>> + u64 linedur_ns, framedur_ns; >>>> int dotclock = mode->crtc_clock; >>>> + unsigned int frame_size; >>>> >>>> if (!drm_dev_has_vblank(dev)) >>>> - return; >>>> + return 0; >>>> >>>> if (drm_WARN_ON(dev, pipe >= dev->num_crtcs)) >>>> - return; >>>> + return -EINVAL; >>>> >>>> - /* Valid dotclock? */ >>>> - if (dotclock > 0) { >>>> - int frame_size = mode->crtc_htotal * mode->crtc_vtotal; >>>> + if (dotclock <= 0) { >>>> + drm_err_once(dev, "crtc %u: Can't calculate constants, dotclock = %d!\n", >>>> + crtc->base.id, dotclock); >>>> + goto error; >>>> + } >>>> >>>> - /* >>>> - * Convert scanline length in pixels and video >>>> - * dot clock to line duration and frame duration >>>> - * in nanoseconds: >>>> - */ >>>> - linedur_ns = div_u64((u64) mode->crtc_htotal * 1000000, dotclock); >>>> - framedur_ns = div_u64((u64) frame_size * 1000000, dotclock); >>>> + frame_size = (unsigned int)mode->crtc_htotal * (unsigned int)mode->crtc_vtotal; >>>> >>>> - /* >>>> - * Fields of interlaced scanout modes are only half a frame duration. >>>> - */ >>>> - if (mode->flags & DRM_MODE_FLAG_INTERLACE) >>>> - framedur_ns /= 2; >>>> - } else { >>>> - drm_err(dev, "crtc %u: Can't calculate constants, dotclock = 0!\n", >>>> - crtc->base.id); >>>> + /* >>>> + * Convert scanline length in pixels and video dot clock to line duration >>>> + * and frame duration in nanoseconds. >>>> + */ >>>> + linedur_ns = div_u64((u64)mode->crtc_htotal * 1000000, dotclock); >>>> + framedur_ns = div_u64((u64)frame_size * 1000000, dotclock); >>>> + >>>> + /* >>>> + * Fields of interlaced scanout modes are only half a frame duration. >>>> + */ >>>> + if (mode->flags & DRM_MODE_FLAG_INTERLACE) >>>> + framedur_ns /= 2; >>>> + >>>> + if (linedur_ns > INT_MAX || framedur_ns > INT_MAX) { >>>> + drm_err_once(dev, "crtc %u: Can't calculate constants, mode clock too small!\n", >>>> + crtc->base.id); >>>> + goto error; >>>> } >>>> >>>> - vblank->linedur_ns = linedur_ns; >>>> + vblank->linedur_ns = linedur_ns; >>>> vblank->framedur_ns = framedur_ns; >>>> drm_mode_copy(&vblank->hwmode, mode); >>>> >>>> @@ -678,7 +687,16 @@ void drm_calc_timestamping_constants(struct drm_crtc *crtc, >>>> crtc->base.id, mode->crtc_htotal, >>>> mode->crtc_vtotal, mode->crtc_vdisplay); >>>> drm_dbg_core(dev, "crtc %u: clock %d kHz framedur %d linedur %d\n", >>>> - crtc->base.id, dotclock, framedur_ns, linedur_ns); >>>> + crtc->base.id, dotclock, >>>> + vblank->framedur_ns, vblank->linedur_ns); >>>> + >>>> + return 0; >>>> + >>>> +error: >>>> + vblank->linedur_ns = 0; >>>> + vblank->framedur_ns = 0; >>>> + drm_mode_copy(&vblank->hwmode, mode); >>>> + return -EINVAL; >>>> } >>>> EXPORT_SYMBOL(drm_calc_timestamping_constants); >>>> >>>> @@ -2221,6 +2239,7 @@ 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; >>>> + int ret; >>>> >>>> if (!vtimer->crtc) { >>>> /* >>>> @@ -2239,7 +2258,9 @@ int drm_crtc_vblank_start_timer(struct drm_crtc *crtc) >>>> hrtimer_try_to_cancel(&vtimer->timer); >>>> } >>>> >>>> - drm_calc_timestamping_constants(crtc, &crtc->mode); >>>> + ret = drm_calc_timestamping_constants(crtc, &crtc->mode); >>>> + if (ret) >>>> + return ret; >>>> >>>> spin_lock_irqsave(&vtimer->interval_lock, flags); >>>> vtimer->interval = ns_to_ktime(vblank->framedur_ns); >>>> diff --git a/include/drm/drm_vblank.h b/include/drm/drm_vblank.h >>>> index 2fcef9c0f5b1..d99772dfa034 100644 >>>> --- a/include/drm/drm_vblank.h >>>> +++ b/include/drm/drm_vblank.h >>>> @@ -311,8 +311,8 @@ void drm_crtc_vblank_on(struct drm_crtc *crtc); >>>> u64 drm_crtc_accurate_vblank_count(struct drm_crtc *crtc); >>>> void drm_crtc_vblank_restore(struct drm_crtc *crtc); >>>> >>>> -void drm_calc_timestamping_constants(struct drm_crtc *crtc, >>>> - const struct drm_display_mode *mode); >>>> +int drm_calc_timestamping_constants(struct drm_crtc *crtc, >>>> + const struct drm_display_mode *mode); >>>> wait_queue_head_t *drm_crtc_vblank_waitqueue(struct drm_crtc *crtc); >>>> void drm_crtc_set_max_vblank_count(struct drm_crtc *crtc, >>>> u32 max_vblank_count); >>>> >>>> base-commit: 06576c2b43750d990297b8ff1989b94ac72e855f >>>> prerequisite-patch-id: 6729bb10d5e338678e78ccda2dd24f232a13cd22 >>>> prerequisite-patch-id: 9d94d20bd45967493b4dc339b58082e1c8da0f4f >>>> prerequisite-patch-id: 6ac8d22df34db9e7d467b1ab95ad30f94841d5db >>>> prerequisite-patch-id: f548dbde3f944ca3415c808be628719acebe898a >>>> prerequisite-patch-id: 2ae91ee8e2bbcd33b8325be046e7214185aba6aa >>>> prerequisite-patch-id: f97bbde08a9996d7c62ca3e3ebfcd387e50bc5c3 >>>> prerequisite-patch-id: ecc3c825277bc9978c6979455517249a074c0cb1 >>>> prerequisite-patch-id: 0449925cca36664b566f614bfb7d573a11f0575f >>>> prerequisite-patch-id: 9fb2c2787244e9621847af09bea03e575e6276c7 >>>> prerequisite-patch-id: eb4384d581805e1178d22e7df45f134223c38305 >>>> prerequisite-patch-id: 1a7a529e4b2864abe8071aa44598d28ce72483f4 >>>> prerequisite-patch-id: 844a85f4c2fe2f72209b82d46614004c3cfe4123 >>>> prerequisite-patch-id: 13f7cb22114c648d35f8c9fa74e9165656ce31eb >>>> prerequisite-patch-id: 20ee46bd6fdd91e39f71d22f52ef6682a4e41512 >>>> prerequisite-patch-id: 1d26c0ff91796ff1e62d3831d00bee18723788ef >>>> prerequisite-patch-id: 007f6ea0d09bef54b689bae0eb5abeb497bdfaad >>>> prerequisite-patch-id: 2d89c1507c909520adf9bfb1a137d458a819ae8f >>>> prerequisite-patch-id: d90dc4134468870ab8ccbd71547c9552dff74fbd >>>> prerequisite-patch-id: c67e5d886a47b7d0266d81100837557fda34cb24 >>>> prerequisite-patch-id: a5a973e527c88a5b47053d7a72aefe0b550197cb >>>> prerequisite-patch-id: b9adc9622920a3e70168e672c2c92795b3e3a106 >>>> prerequisite-patch-id: 5030de433a01c2e99056cadb676a8e2ba35f055a >>>> -- >>>> 2.55.0 >> -- >> -- >> Thomas Zimmermann >> Graphics Driver Developer >> SUSE Software Solutions Germany GmbH >> Frankenstr. 146, 90461 Nürnberg, Germany, www.suse.com >> GF: Stefan Gaiser, Jochen Jaser, Abhinav Puri, (HRB 36809, AG Nürnberg) >> -- -- Thomas Zimmermann Graphics Driver Developer SUSE Software Solutions Germany GmbH Frankenstr. 146, 90461 Nürnberg, Germany, www.suse.com GF: Stefan Gaiser, Jochen Jaser, Abhinav Puri, (HRB 36809, AG Nürnberg) ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v4] drm/vblank: Don't arm vblank timer with invalid frame duration 2026-10-30 13:04 ` Thomas Zimmermann @ 2026-10-30 13:48 ` Ville Syrjälä 2026-10-30 14:25 ` Thomas Zimmermann 0 siblings, 1 reply; 14+ messages in thread From: Ville Syrjälä @ 2026-10-30 13:48 UTC (permalink / raw) To: Thomas Zimmermann Cc: me, louis.chauvet, maarten.lankhorst, mripard, airlied, simona, javierm, dmitry.osipenko, regressions, peter.m.arnesen, dri-devel, virtualization, sashiko-reviews On Fri, Oct 30, 2026 at 02:04:04PM +0100, Thomas Zimmermann wrote: > Hi > > Am 30.10.26 um 13:07 schrieb Ville Syrjälä: > > On Thu, Oct 29, 2026 at 12:35:52PM +0100, Thomas Zimmermann wrote: > >> Hi > >> > >> Am 28.10.26 um 15:54 schrieb Ville Syrjälä: > >>> On Mon, Sep 14, 2026 at 12:57:26PM +0200, Thomas Zimmermann wrote: > >>>> From: Roman Ilin <me@romanilin.is> > >>>> > >>>> When a CRTC's display mode carries a too small pixel clock, > >>>> drm_calc_timestamping_constants() computes a frame duration that > >>>> exceeds INT_MAX. drm_vblank_crtc.framedur_ns becomes negative. > >>>> drm_crtc_vblank_start_timer() then arms the vblank hrtimer with this > >>>> interval, after which vblank events are no longer delivered. Pending > >>>> page flips never complete and the display appears frozen. > >>>> > >>>> This could be triggered on virtio-gpu guests that have dynamic resolution > >>>> enabled: when the SPICE agent or the X server resizes the output, it > >>>> submits a mode whose pixel clock is off by a factor of 1000, e.g.: > >>>> > >>>> clock = 406 kHz, htotal = 3152, vtotal = 2148 > >>> How about actually "fixing" the clock in the driver? > >> It's not a broken driver, but user space. There is a bug report open for > >> spice to replace the incorrect units with correct ones. [1] The fix here > >> is supposed to resolve the kernel problem this triggers. > > Life becomes much simpler if you don't let garbage userspace input > > past some initial sanity checks. So technically the driver should > > just reject these modes. But in this case since you need to keep > > the broken userspace working the driver should just fix it up, > > and do so early so you don't have to deal with it anywhere else. > > Not that I disagree, with the statement in general. But doing this here > seems like whack-a-mole if we leave it to individual drivers. I think you're just trading that whack-a-mole for a worse one where you now have to make all kinds of low level functions deal with the garbage input. This particular function is never expected to fail, which is why it returns void. And where it gets called in i915 we can't even handle the failure, so changing the return type to int is making the whole thing confusing. One driver independent solution would be to have drm_mode_validate_basic() validate the dotclock a bit hadder, but that will not work for this case because you need to keep allowing these bogus modes to reach the driver. Just the other day I was actually thinking of adding some basic frame duration checks into drm_mode_validate_basic(), but I guess I can't do that given this userspace bug. > And the > userspace is already dis-functional, no need to keep supporting it, I think. > > And how would drivers know that the values are garbage before processing > them? That's why I added the device wide mode_valid hook. Drivers can use that to reject garbage before it reaches anything more sensitive. I have somewhat hard time believing a mode that is acceptable/reasonable to some real world hardware would fail in the vblank calculations. Hmm, and I just realized that I haven't actually added any low limit for the dotclock in i915. I'll go do that now. I think ~10 MHz is probably a reasonable enough number I can use there. > Even if a driver could program them to hardware, the vblank > helpers could still not process them correctly. Thus leaving the > userspace dis-functional. > > I see that we might want a common test function > drm_vblank_check_display_mode() and put it in some central place in the > atomic_check pipeline. At least we'd separate the testing from the > calculation. Yeah, something like that could work. Though I still think it'd be better to do it from the driver's mode_valid hook to reject the bogus stuff even earlier. That would also filter them out from the connectors' mode lists. But I suppose you could do the vblank check in the connector mode filtering unconditionally without having the driver do it. Or does the buggy userspace also need the kernel to return bogus modes from getconnector? -- Ville Syrjälä Intel ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v4] drm/vblank: Don't arm vblank timer with invalid frame duration 2026-10-30 13:48 ` Ville Syrjälä @ 2026-10-30 14:25 ` Thomas Zimmermann 2026-10-30 15:33 ` Thomas Zimmermann 0 siblings, 1 reply; 14+ messages in thread From: Thomas Zimmermann @ 2026-10-30 14:25 UTC (permalink / raw) To: Ville Syrjälä Cc: me, louis.chauvet, maarten.lankhorst, mripard, airlied, simona, javierm, dmitry.osipenko, regressions, peter.m.arnesen, dri-devel, virtualization, sashiko-reviews Hi Am 30.10.26 um 14:48 schrieb Ville Syrjälä: [...] >> And how would drivers know that the values are garbage before processing >> them? > That's why I added the device wide mode_valid hook. Drivers can > use that to reject garbage before it reaches anything more > sensitive. I have somewhat hard time believing a mode that is > acceptable/reasonable to some real world hardware would fail > in the vblank calculations. I can think of some drivers with fixed display mode, such as a panel or the sysfb drivers. If they use vblank timers they might run into that situation. But it's really a corner case. > > Hmm, and I just realized that I haven't actually added any low limit > for the dotclock in i915. I'll go do that now. I think ~10 MHz is > probably a reasonable enough number I can use there. > >> Even if a driver could program them to hardware, the vblank >> helpers could still not process them correctly. Thus leaving the >> userspace dis-functional. >> >> I see that we might want a common test function >> drm_vblank_check_display_mode() and put it in some central place in the >> atomic_check pipeline. At least we'd separate the testing from the >> calculation. > Yeah, something like that could work. Though I still > think it'd be better to do it from the driver's mode_valid > hook to reject the bogus stuff even earlier. > > That would also filter them out from the connectors' mode > lists. But I suppose you could do the vblank check in > the connector mode filtering unconditionally without > having the driver do it. Or does the buggy userspace also > need the kernel to return bogus modes from getconnector? How about we add a dedicated mode_fixup helper for vblank validation? It would run after new CRTC's mode_fixup callback. [1] There we could try to adapt the CRTC's adjusted_mode. Since we know the bug exactly, we can try to detect it, fix it, and document it. [1] https://elixir.bootlin.com/linux/v7.3/source/drivers/gpu/drm/drm_atomic_helper.c#L508 Best regards Thomas > -- -- Thomas Zimmermann Graphics Driver Developer SUSE Software Solutions Germany GmbH Frankenstr. 146, 90461 Nürnberg, Germany, www.suse.com GF: Stefan Gaiser, Jochen Jaser, Abhinav Puri, (HRB 36809, AG Nürnberg) ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v4] drm/vblank: Don't arm vblank timer with invalid frame duration 2026-10-30 14:25 ` Thomas Zimmermann @ 2026-10-30 15:33 ` Thomas Zimmermann 2026-10-30 15:48 ` sashiko-bot ` (2 more replies) 0 siblings, 3 replies; 14+ messages in thread From: Thomas Zimmermann @ 2026-10-30 15:33 UTC (permalink / raw) To: Ville Syrjälä Cc: me, louis.chauvet, maarten.lankhorst, mripard, airlied, simona, javierm, dmitry.osipenko, regressions, peter.m.arnesen, dri-devel, virtualization, sashiko-reviews [-- Attachment #1: Type: text/plain, Size: 776 bytes --] Am 30.10.26 um 15:25 schrieb Thomas Zimmermann: [...] > How about we add a dedicated mode_fixup helper for vblank validation? > It would run after new CRTC's mode_fixup callback. [1] There we could > try to adapt the CRTC's adjusted_mode. Since we know the bug exactly, > we can try to detect it, fix it, and document it. > The attached patch implements this idea. It does work in the sense that it doesn't immediately break my testing setup. Thorsten (or anyone else) could you please test if this patch also resolve the bug with SPICE? >> > -- -- Thomas Zimmermann Graphics Driver Developer SUSE Software Solutions Germany GmbH Frankenstr. 146, 90461 Nürnberg, Germany, www.suse.com GF: Stefan Gaiser, Jochen Jaser, Abhinav Puri, (HRB 36809, AG Nürnberg) [-- Attachment #2: 0001-drm-vblank-Don-t-arm-vblank-timer-with-invalid-frame.patch --] [-- Type: text/x-patch, Size: 6100 bytes --] From 2a76d04bfc3630c1d6bb79b3d64ecbcadf860090 Mon Sep 17 00:00:00 2001 From: Roman Ilin <me@romanilin.is> Date: Thu, 2 Jul 2026 21:10:27 +0300 Subject: [PATCH] drm/vblank: Don't arm vblank timer with invalid frame duration When a CRTC's display mode carries a too small pixel clock, drm_calc_timestamping_constants() computes a frame duration that exceeds INT_MAX. drm_vblank_crtc.framedur_ns becomes negative. drm_crtc_vblank_start_timer() then arms the vblank hrtimer with this interval, after which vblank events are no longer delivered. Pending page flips never complete and the display appears frozen. This could be triggered on virtio-gpu guests that have dynamic resolution enabled: when the SPICE agent or the X server resizes the output, it submits a mode whose pixel clock is off by a factor of 1000, e.g.: clock = 406 kHz, htotal = 3152, vtotal = 2148 framedur_ns = 3152 * 2148 * 1000000 / 406 = 16675852216 ns (~16.7 s) 16675852216 does not fit into an int and wraps to roughly -504000000. ns_to_ktime() then yields a negative interval and the timer stops working. Found by bisection, which pointed at commit a036f5fceedb ("drm/virtgpu: Use vblank timer"). That commit merely made virtio-gpu use the vblank timer and thereby exposed the pre-existing problem in the timer setup added by commit 74afeb812850 ("drm/vblank: Add vblank timer"). To fix this, introduce drm_crtc_vblank_mode_fixup() to detect and adjust the bogus pixel clock. The helper runs unconditionally at the beginning of the mode-fixup code. Drivers still have the opportunity to detect bogus adjustments in later mode-fixup steps. v5: - fix up the broken pixel clock beforehand (Ville) v4: - avoid log spamming with drm_warn_once() Fixes: 74afeb812850 ("drm/vblank: Add vblank timer") Signed-off-by: Roman Ilin <me@romanilin.is> mode_fixup Revert "drm/vblank: Don't arm vblank timer with invalid frame duration" This reverts commit f3ca78682a4f9607444a3dafbe12ab6acdb1fa2f. --- drivers/gpu/drm/drm_atomic_helper.c | 11 +++++ drivers/gpu/drm/drm_vblank.c | 68 +++++++++++++++++++++++++++++ include/drm/drm_vblank.h | 2 + 3 files changed, 81 insertions(+) diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c index 9d006f98413a..6a0f04a72292 100644 --- a/drivers/gpu/drm/drm_atomic_helper.c +++ b/drivers/gpu/drm/drm_atomic_helper.c @@ -432,6 +432,17 @@ mode_fixup(struct drm_atomic_commit *state) continue; drm_mode_copy(&new_crtc_state->adjusted_mode, &new_crtc_state->mode); + + if (!new_crtc_state->enable) + continue; + + ret = drm_crtc_vblank_mode_fixup(crtc, &new_crtc_state->mode, + &new_crtc_state->adjusted_mode); + if (!ret) { + drm_dbg_atomic(crtc->dev, "[CRTC:%d:%s] vblank fixup failed\n", + crtc->base.id, crtc->name); + return -EINVAL; + } } for_each_new_connector_in_state(state, connector, new_conn_state, i) { diff --git a/drivers/gpu/drm/drm_vblank.c b/drivers/gpu/drm/drm_vblank.c index f90fb2d13e42..6e826c3b6c42 100644 --- a/drivers/gpu/drm/drm_vblank.c +++ b/drivers/gpu/drm/drm_vblank.c @@ -620,6 +620,74 @@ wait_queue_head_t *drm_crtc_vblank_waitqueue(struct drm_crtc *crtc) } EXPORT_SYMBOL(drm_crtc_vblank_waitqueue); +static int drm_vblank_validate_dotclock(u16 crtc_htotal, u16 crtc_vtotal, u32 flags, int dotclock) +{ + unsigned int frame_size; + u64 linedur_ns, framedur_ns; + + if (dotclock <= 0) + return -EINVAL; + + frame_size = (unsigned int)crtc_htotal * (unsigned int)crtc_vtotal; + + /* + * Convert scanline length in pixels and video dot clock to line duration + * and frame duration in nanoseconds. + */ + linedur_ns = div_u64((u64)crtc_htotal * 1000000, dotclock); + framedur_ns = div_u64((u64)frame_size * 1000000, dotclock); + + /* + * Fields of interlaced scanout modes are only half a frame duration. + */ + if (flags & DRM_MODE_FLAG_INTERLACE) + framedur_ns /= 2; + + if (linedur_ns > INT_MAX || framedur_ns > INT_MAX) + return -ERANGE; /* dot clock too low */ + + return 0; +} + +bool drm_crtc_vblank_mode_fixup(struct drm_crtc *crtc, const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct drm_device *dev = crtc->dev; + unsigned int pipe = drm_crtc_index(crtc); + int dotclock = adjusted_mode->crtc_clock; + int ret; + + if (!drm_dev_has_vblank(dev)) + return true; + + if (drm_WARN_ON(dev, pipe >= dev->num_crtcs)) + return false; + + ret = drm_vblank_validate_dotclock(adjusted_mode->crtc_htotal, + adjusted_mode->crtc_vtotal, + adjusted_mode->flags, dotclock); + if (ret == -ERANGE) { + int dotclock_1000; + + /* + * SPICE' vd_agent has a bug where it specifies the dot clock in kHz + * instead of Hz. Setting up a vblank interrupt or timer for such a + * mode overflows the calculation in drm_calc_timestamping_constants(). + * An errno code of ERANGE specifically points to this problem. Try + * converting the dot clock to Hz and validate again. + */ + if (check_mul_overflow(dotclock, 1000, &dotclock_1000)) { + ret = drm_vblank_validate_dotclock(adjusted_mode->crtc_htotal, + adjusted_mode->crtc_vtotal, + adjusted_mode->flags, dotclock_1000); + if (!ret) + adjusted_mode->crtc_clock = dotclock_1000; + } + } + + return !ret; +} +EXPORT_SYMBOL(drm_crtc_vblank_mode_fixup); /** * drm_calc_timestamping_constants - calculate vblank timestamp constants diff --git a/include/drm/drm_vblank.h b/include/drm/drm_vblank.h index 2fcef9c0f5b1..a3628c477f59 100644 --- a/include/drm/drm_vblank.h +++ b/include/drm/drm_vblank.h @@ -311,6 +311,8 @@ void drm_crtc_vblank_on(struct drm_crtc *crtc); u64 drm_crtc_accurate_vblank_count(struct drm_crtc *crtc); void drm_crtc_vblank_restore(struct drm_crtc *crtc); +bool drm_crtc_vblank_mode_fixup(struct drm_crtc *crtc, const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode); void drm_calc_timestamping_constants(struct drm_crtc *crtc, const struct drm_display_mode *mode); wait_queue_head_t *drm_crtc_vblank_waitqueue(struct drm_crtc *crtc); -- 2.56.0 ^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [PATCH v4] drm/vblank: Don't arm vblank timer with invalid frame duration 2026-10-30 15:33 ` Thomas Zimmermann @ 2026-10-30 15:48 ` sashiko-bot 2026-10-30 15:58 ` Thorsten Leemhuis 2026-10-30 16:06 ` Ville Syrjälä 2 siblings, 0 replies; 14+ messages in thread From: sashiko-bot @ 2026-10-30 15:48 UTC (permalink / raw) To: Thomas Zimmermann Cc: dri-devel, virtualization, Michael S. Tsirkin, Eugenio Perez Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider: - [High] drm/vblank: inverted check_mul_overflow() in drm_crtc_vblank_mode_fixup() - [High] drm/vblank: drm_crtc_vblank_start_timer() overwrites fixed-up framedur_ns - [High] drm/atomic-helper: premature drm_crtc_vblank_mode_fixup() in mode_fixup() - [Low] drm/vblank: uncleaned email reply, revert notes, and missing author sign-off -- commit a8f16692f361f62f9c1ca7a34a1c2c7c62ba5c70 Author: Thomas Zimmermann <tzimmermann@suse.de> drm/vblank: Don't arm vblank timer with invalid frame duration This commit introduces drm_crtc_vblank_mode_fixup() and calls it from mode_fixup() in drm_atomic_helper.c to validate and adjust display mode dotclocks that would otherwise overflow the frame duration calculation in drm_calc_timestamping_constants(). > drm/vblank: Don't arm vblank timer with invalid frame duration > > Am 30.10.26 um 15:25 schrieb Thomas Zimmermann: > [...] > > How about we add a dedicated mode_fixup helper for vblank validation? > > It would run after new CRTC's mode_fixup callback. [1] There we could > > try to adapt the CRTC's adjusted_mode. Since we know the bug exactly, > > we can try to detect it, fix it, and document it. > > > > The attached patch implements this idea. It does work in the sense that > it doesn't immediately break my testing setup. > > Thorsten (or anyone else) could you please test if this patch also > resolve the bug with SPICE? > > >> > > > > -- > -- > Thomas Zimmermann > Graphics Driver Developer > SUSE Software Solutions Germany GmbH > Frankenstr. 146, 90461 Nürnberg, Germany, www.suse.com > GF: Stefan Gaiser, Jochen Jaser, Abhinav Puri, (HRB 36809, AG Nürnberg) > > When a CRTC's display mode carries a too small pixel clock, > drm_calc_timestamping_constants() computes a frame duration that > exceeds INT_MAX. drm_vblank_crtc.framedur_ns becomes negative. > drm_crtc_vblank_start_timer() then arms the vblank hrtimer with this > interval, after which vblank events are no longer delivered. Pending > page flips never complete and the display appears frozen. > > This could be triggered on virtio-gpu guests that have dynamic resolution > enabled: when the SPICE agent or the X server resizes the output, it > submits a mode whose pixel clock is off by a factor of 1000, e.g.: > > clock = 406 kHz, htotal = 3152, vtotal = 2148 > > framedur_ns = 3152 * 2148 * 1000000 / 406 = 16675852216 ns (~16.7 s) > > 16675852216 does not fit into an int and wraps to roughly -504000000. > ns_to_ktime() then yields a negative interval and the timer stops working. > > Found by bisection, which pointed at commit a036f5fceedb ("drm/virtgpu: > Use vblank timer"). That commit merely made virtio-gpu use the vblank > timer and thereby exposed the pre-existing problem in the timer setup > added by commit 74afeb812850 ("drm/vblank: Add vblank timer"). > > To fix this, introduce drm_crtc_vblank_mode_fixup() to detect and adjust > the bogus pixel clock. The helper runs unconditionally at the beginning > of the mode-fixup code. Drivers still have the opportunity to detect bogus > adjustments in later mode-fixup steps. > > v5: > - fix up the broken pixel clock beforehand (Ville) > v4: > - avoid log spamming with drm_warn_once() > > Fixes: 74afeb812850 ("drm/vblank: Add vblank timer") > Signed-off-by: Roman Ilin <me@romanilin.is> > > mode_fixup > > Revert "drm/vblank: Don't arm vblank timer with invalid frame duration" > > This reverts commit f3ca78682a4f9607444a3dafbe12ab6acdb1fa2f. [Severity: Low] This isn't a bug in the code, but the commit message includes an uncleaned email reply header and signature block at the top, trailing git squash and revert scratch notes at the bottom, and is missing a Signed-off-by tag from the commit author. Should the commit message be cleaned up and include the author's sign-off? > diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c > index 9d006f98413a5..6a0f04a72292b 100644 > --- a/drivers/gpu/drm/drm_atomic_helper.c > +++ b/drivers/gpu/drm/drm_atomic_helper.c > @@ -432,6 +432,17 @@ mode_fixup(struct drm_atomic_commit *state) > continue; > > drm_mode_copy(&new_crtc_state->adjusted_mode, &new_crtc_state->mode); > + > + if (!new_crtc_state->enable) > + continue; > + > + ret = drm_crtc_vblank_mode_fixup(crtc, &new_crtc_state->mode, > + &new_crtc_state->adjusted_mode); > + if (!ret) { > + drm_dbg_atomic(crtc->dev, "[CRTC:%d:%s] vblank fixup failed\n", > + crtc->base.id, crtc->name); > + return -EINVAL; > + } > } [Severity: High] Could running drm_crtc_vblank_mode_fixup() at the beginning of mode_fixup() before bridge, encoder, and CRTC callbacks cause regressions for callers that populate or recompute crtc_clock later in the atomic check? For example, in-kernel callers and KUnit tests such as vc4_mock_atomic_add_output() in drivers/gpu/drm/vc4/tests/vc4_mock_output.c set up a mode with DRM_SIMPLE_MODE() where crtc_clock is 0: drivers/gpu/drm/vc4/tests/vc4_mock_output.c:vc4_mock_atomic_add_output() { ... ret = drm_atomic_set_mode_for_crtc(crtc_state, &default_mode); if (ret) return ret; ... } Similarly, vmw_du_crtc_atomic_check() in drivers/gpu/drm/vmwgfx/vmwgfx_kms.c populates adjusted_mode.crtc_clock during crtc->atomic_check(), which runs in drm_atomic_helper_check_planes() after mode_fixup(): drivers/gpu/drm/vmwgfx/vmwgfx_kms.c:vmw_du_crtc_atomic_check() { ... if (new_state->mode.crtc_clock == 0) new_state->adjusted_mode.crtc_clock = new_state->mode.clock; ... } Because adjusted_mode.crtc_clock is still 0 at the start of mode_fixup(), drm_vblank_validate_dotclock() will return -EINVAL and fail the check before those callbacks run. In addition, drm_crtc_vblank_mode_fixup() only adjusts adjusted_mode->crtc_clock while leaving adjusted_mode->clock unchanged. If a subsequent bridge, encoder, or CRTC mode_fixup() callback calls drm_mode_set_crtcinfo(adjusted_mode, ...), won't that overwrite adjusted_mode->crtc_clock with the unadjusted adjusted_mode->clock and leave any driver-adjusted timings unvalidated? > diff --git a/drivers/gpu/drm/drm_vblank.c b/drivers/gpu/drm/drm_vblank.c > index f90fb2d13e423..6e826c3b6c425 100644 > --- a/drivers/gpu/drm/drm_vblank.c > +++ b/drivers/gpu/drm/drm_vblank.c > @@ -620,6 +620,74 @@ wait_queue_head_t *drm_crtc_vblank_waitqueue(struct drm_crtc *crtc) > } > EXPORT_SYMBOL(drm_crtc_vblank_waitqueue); > > +static int drm_vblank_validate_dotclock(u16 crtc_htotal, u16 crtc_vtotal, u32 flags, int dotclock) > +{ > + unsigned int frame_size; > + u64 linedur_ns, framedur_ns; > + > + if (dotclock <= 0) > + return -EINVAL; > + > + frame_size = (unsigned int)crtc_htotal * (unsigned int)crtc_vtotal; > + > + /* > + * Convert scanline length in pixels and video dot clock to line duration > + * and frame duration in nanoseconds. > + */ > + linedur_ns = div_u64((u64)crtc_htotal * 1000000, dotclock); > + framedur_ns = div_u64((u64)frame_size * 1000000, dotclock); > + > + /* > + * Fields of interlaced scanout modes are only half a frame duration. > + */ > + if (flags & DRM_MODE_FLAG_INTERLACE) > + framedur_ns /= 2; > + > + if (linedur_ns > INT_MAX || framedur_ns > INT_MAX) > + return -ERANGE; /* dot clock too low */ > + > + return 0; > +} > + > +bool drm_crtc_vblank_mode_fixup(struct drm_crtc *crtc, const struct drm_display_mode *mode, > + struct drm_display_mode *adjusted_mode) > +{ > + struct drm_device *dev = crtc->dev; > + unsigned int pipe = drm_crtc_index(crtc); > + int dotclock = adjusted_mode->crtc_clock; > + int ret; > + > + if (!drm_dev_has_vblank(dev)) > + return true; > + > + if (drm_WARN_ON(dev, pipe >= dev->num_crtcs)) > + return false; > + > + ret = drm_vblank_validate_dotclock(adjusted_mode->crtc_htotal, > + adjusted_mode->crtc_vtotal, > + adjusted_mode->flags, dotclock); > + if (ret == -ERANGE) { > + int dotclock_1000; > + > + /* > + * SPICE' vd_agent has a bug where it specifies the dot clock in kHz > + * instead of Hz. Setting up a vblank interrupt or timer for such a > + * mode overflows the calculation in drm_calc_timestamping_constants(). > + * An errno code of ERANGE specifically points to this problem. Try > + * converting the dot clock to Hz and validate again. > + */ > + if (check_mul_overflow(dotclock, 1000, &dotclock_1000)) { [Severity: High] Is the check_mul_overflow() condition inverted here? check_mul_overflow() returns true when the multiplication overflows and false when it succeeds without overflow. When SPICE vd_agent passes a small dotclock such as 406, multiplying 406 by 1000 does not overflow, so check_mul_overflow(dotclock, 1000, &dotclock_1000) returns false. As a result, the fixup block is skipped, ret remains -ERANGE, drm_crtc_vblank_mode_fixup() returns false, and mode_fixup() rejects the modeset with -EINVAL. > + ret = drm_vblank_validate_dotclock(adjusted_mode->crtc_htotal, > + adjusted_mode->crtc_vtotal, > + adjusted_mode->flags, dotclock_1000); > + if (!ret) > + adjusted_mode->crtc_clock = dotclock_1000; [Severity: High] Even if adjusted_mode->crtc_clock is updated here, won't drm_crtc_vblank_start_timer() later overwrite vblank->framedur_ns using the unadjusted crtc->mode? During an atomic commit, drm_atomic_helper_update_legacy_modeset_state() in drivers/gpu/drm/drm_atomic_helper.c copies the unadjusted mode into crtc->mode: drivers/gpu/drm/drm_atomic_helper.c:drm_atomic_helper_update_legacy_modeset_state() { ... for_each_new_crtc_in_state(state, crtc, new_crtc_state, i) { ... crtc->mode = new_crtc_state->mode; crtc->enabled = new_crtc_state->enable; ... } } Although drm_atomic_helper_calc_timestamping_constants() calculates vblank->framedur_ns from new_crtc_state->adjusted_mode, whenever vblank is subsequently enabled on the CRTC via drm_crtc_vblank_get()->drm_vblank_enable()->__enable_vblank()->drm_crtc_vblank_helper_enable_vblank_timer()->drm_crtc_vblank_start_timer(), drm_crtc_vblank_start_timer() recomputes the timestamping constants from &crtc->mode: drivers/gpu/drm/drm_vblank.c:drm_crtc_vblank_start_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); ... } Since crtc->mode still holds the unadjusted crtc_clock, won't this clobber vblank->framedur_ns with the overflowed negative duration and arm the timer with a negative interval anyway? -- Sashiko AI review · https://sashiko.dev/#/patchset/c874d311-f7b1-4a24-ab75-b502ba1c5e58@suse.de?part=1 ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v4] drm/vblank: Don't arm vblank timer with invalid frame duration 2026-10-30 15:33 ` Thomas Zimmermann 2026-10-30 15:48 ` sashiko-bot @ 2026-10-30 15:58 ` Thorsten Leemhuis 2026-10-30 16:06 ` Ville Syrjälä 2 siblings, 0 replies; 14+ messages in thread From: Thorsten Leemhuis @ 2026-10-30 15:58 UTC (permalink / raw) To: Thomas Zimmermann, Ville Syrjälä Cc: me, louis.chauvet, maarten.lankhorst, mripard, airlied, simona, javierm, dmitry.osipenko, peter.m.arnesen, dri-devel, virtualization, sashiko-reviews On 10/30/26 16:33, Thomas Zimmermann wrote: > Am 30.10.26 um 15:25 schrieb Thomas Zimmermann: > [...] >> How about we add a dedicated mode_fixup helper for vblank validation? >> It would run after new CRTC's mode_fixup callback. [1] There we could >> try to adapt the CRTC's adjusted_mode. Since we know the bug exactly, >> we can try to detect it, fix it, and document it. >> > > The attached patch implements this idea. It does work in the sense that > it doesn't immediately break my testing setup. > > Thorsten (or anyone else) could you please test if this patch also > resolve the bug with SPICE? Sorry, can't help you here, I'm not affected; I only cared because I keep an eye on regressions in general -- and a few weeks ago it looked like all that was needed to resolve this one was a relative easy adjustment and a repost of an already tested fix. Ohh well, that turned out to not be the case. Sorry. Given that the original reporter is MIA (iirc) I wonder if we should just leave things here as they are until someone shows up that is affected who can also test the patch. Ciao, Thorsten ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v4] drm/vblank: Don't arm vblank timer with invalid frame duration 2026-10-30 15:33 ` Thomas Zimmermann 2026-10-30 15:48 ` sashiko-bot 2026-10-30 15:58 ` Thorsten Leemhuis @ 2026-10-30 16:06 ` Ville Syrjälä 2 siblings, 0 replies; 14+ messages in thread From: Ville Syrjälä @ 2026-10-30 16:06 UTC (permalink / raw) To: Thomas Zimmermann Cc: me, louis.chauvet, maarten.lankhorst, mripard, airlied, simona, javierm, dmitry.osipenko, regressions, peter.m.arnesen, dri-devel, virtualization, sashiko-reviews On Fri, Oct 30, 2026 at 04:33:24PM +0100, Thomas Zimmermann wrote: > > > Am 30.10.26 um 15:25 schrieb Thomas Zimmermann: > [...] > > How about we add a dedicated mode_fixup helper for vblank validation? > > It would run after new CRTC's mode_fixup callback. [1] There we could > > try to adapt the CRTC's adjusted_mode. Since we know the bug exactly, > > we can try to detect it, fix it, and document it. > > > > The attached patch implements this idea. It does work in the sense that > it doesn't immediately break my testing setup. > > Thorsten (or anyone else) could you please test if this patch also > resolve the bug with SPICE? > > > > >> > > > > -- > -- > Thomas Zimmermann > Graphics Driver Developer > SUSE Software Solutions Germany GmbH > Frankenstr. 146, 90461 Nürnberg, Germany, www.suse.com > GF: Stefan Gaiser, Jochen Jaser, Abhinav Puri, (HRB 36809, AG Nürnberg) > > From 2a76d04bfc3630c1d6bb79b3d64ecbcadf860090 Mon Sep 17 00:00:00 2001 > From: Roman Ilin <me@romanilin.is> > Date: Thu, 2 Jul 2026 21:10:27 +0300 > Subject: [PATCH] drm/vblank: Don't arm vblank timer with invalid frame > duration > > When a CRTC's display mode carries a too small pixel clock, > drm_calc_timestamping_constants() computes a frame duration that > exceeds INT_MAX. drm_vblank_crtc.framedur_ns becomes negative. > drm_crtc_vblank_start_timer() then arms the vblank hrtimer with this > interval, after which vblank events are no longer delivered. Pending > page flips never complete and the display appears frozen. > > This could be triggered on virtio-gpu guests that have dynamic resolution > enabled: when the SPICE agent or the X server resizes the output, it > submits a mode whose pixel clock is off by a factor of 1000, e.g.: > > clock = 406 kHz, htotal = 3152, vtotal = 2148 > > framedur_ns = 3152 * 2148 * 1000000 / 406 = 16675852216 ns (~16.7 s) > > 16675852216 does not fit into an int and wraps to roughly -504000000. > ns_to_ktime() then yields a negative interval and the timer stops working. > > Found by bisection, which pointed at commit a036f5fceedb ("drm/virtgpu: > Use vblank timer"). That commit merely made virtio-gpu use the vblank > timer and thereby exposed the pre-existing problem in the timer setup > added by commit 74afeb812850 ("drm/vblank: Add vblank timer"). > > To fix this, introduce drm_crtc_vblank_mode_fixup() to detect and adjust > the bogus pixel clock. The helper runs unconditionally at the beginning > of the mode-fixup code. Drivers still have the opportunity to detect bogus > adjustments in later mode-fixup steps. > > v5: > - fix up the broken pixel clock beforehand (Ville) > v4: > - avoid log spamming with drm_warn_once() > > Fixes: 74afeb812850 ("drm/vblank: Add vblank timer") > Signed-off-by: Roman Ilin <me@romanilin.is> > > mode_fixup > > Revert "drm/vblank: Don't arm vblank timer with invalid frame duration" > > This reverts commit f3ca78682a4f9607444a3dafbe12ab6acdb1fa2f. > --- > drivers/gpu/drm/drm_atomic_helper.c | 11 +++++ > drivers/gpu/drm/drm_vblank.c | 68 +++++++++++++++++++++++++++++ > include/drm/drm_vblank.h | 2 + > 3 files changed, 81 insertions(+) > > diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c > index 9d006f98413a..6a0f04a72292 100644 > --- a/drivers/gpu/drm/drm_atomic_helper.c > +++ b/drivers/gpu/drm/drm_atomic_helper.c > @@ -432,6 +432,17 @@ mode_fixup(struct drm_atomic_commit *state) > continue; > > drm_mode_copy(&new_crtc_state->adjusted_mode, &new_crtc_state->mode); > + > + if (!new_crtc_state->enable) > + continue; > + > + ret = drm_crtc_vblank_mode_fixup(crtc, &new_crtc_state->mode, > + &new_crtc_state->adjusted_mode); We don't want this hack applied to all drivers. It's a problem for a single driver, so that one driver should handle it. > + if (!ret) { > + drm_dbg_atomic(crtc->dev, "[CRTC:%d:%s] vblank fixup failed\n", > + crtc->base.id, crtc->name); > + return -EINVAL; > + } > } > > for_each_new_connector_in_state(state, connector, new_conn_state, i) { > diff --git a/drivers/gpu/drm/drm_vblank.c b/drivers/gpu/drm/drm_vblank.c > index f90fb2d13e42..6e826c3b6c42 100644 > --- a/drivers/gpu/drm/drm_vblank.c > +++ b/drivers/gpu/drm/drm_vblank.c > @@ -620,6 +620,74 @@ wait_queue_head_t *drm_crtc_vblank_waitqueue(struct drm_crtc *crtc) > } > EXPORT_SYMBOL(drm_crtc_vblank_waitqueue); > > +static int drm_vblank_validate_dotclock(u16 crtc_htotal, u16 crtc_vtotal, u32 flags, int dotclock) > +{ > + unsigned int frame_size; > + u64 linedur_ns, framedur_ns; > + > + if (dotclock <= 0) > + return -EINVAL; > + > + frame_size = (unsigned int)crtc_htotal * (unsigned int)crtc_vtotal; > + > + /* > + * Convert scanline length in pixels and video dot clock to line duration > + * and frame duration in nanoseconds. > + */ > + linedur_ns = div_u64((u64)crtc_htotal * 1000000, dotclock); > + framedur_ns = div_u64((u64)frame_size * 1000000, dotclock); > + > + /* > + * Fields of interlaced scanout modes are only half a frame duration. > + */ > + if (flags & DRM_MODE_FLAG_INTERLACE) > + framedur_ns /= 2; > + > + if (linedur_ns > INT_MAX || framedur_ns > INT_MAX) > + return -ERANGE; /* dot clock too low */ > + > + return 0; > +} > + > +bool drm_crtc_vblank_mode_fixup(struct drm_crtc *crtc, const struct drm_display_mode *mode, > + struct drm_display_mode *adjusted_mode) > +{ > + struct drm_device *dev = crtc->dev; > + unsigned int pipe = drm_crtc_index(crtc); > + int dotclock = adjusted_mode->crtc_clock; > + int ret; > + > + if (!drm_dev_has_vblank(dev)) > + return true; > + > + if (drm_WARN_ON(dev, pipe >= dev->num_crtcs)) > + return false; > + > + ret = drm_vblank_validate_dotclock(adjusted_mode->crtc_htotal, > + adjusted_mode->crtc_vtotal, > + adjusted_mode->flags, dotclock); > + if (ret == -ERANGE) { > + int dotclock_1000; > + > + /* > + * SPICE' vd_agent has a bug where it specifies the dot clock in kHz > + * instead of Hz. Setting up a vblank interrupt or timer for such a > + * mode overflows the calculation in drm_calc_timestamping_constants(). > + * An errno code of ERANGE specifically points to this problem. Try > + * converting the dot clock to Hz and validate again. > + */ > + if (check_mul_overflow(dotclock, 1000, &dotclock_1000)) { > + ret = drm_vblank_validate_dotclock(adjusted_mode->crtc_htotal, > + adjusted_mode->crtc_vtotal, > + adjusted_mode->flags, dotclock_1000); > + if (!ret) > + adjusted_mode->crtc_clock = dotclock_1000; > + } > + } > + > + return !ret; > +} > +EXPORT_SYMBOL(drm_crtc_vblank_mode_fixup); > > /** > * drm_calc_timestamping_constants - calculate vblank timestamp constants > diff --git a/include/drm/drm_vblank.h b/include/drm/drm_vblank.h > index 2fcef9c0f5b1..a3628c477f59 100644 > --- a/include/drm/drm_vblank.h > +++ b/include/drm/drm_vblank.h > @@ -311,6 +311,8 @@ void drm_crtc_vblank_on(struct drm_crtc *crtc); > u64 drm_crtc_accurate_vblank_count(struct drm_crtc *crtc); > void drm_crtc_vblank_restore(struct drm_crtc *crtc); > > +bool drm_crtc_vblank_mode_fixup(struct drm_crtc *crtc, const struct drm_display_mode *mode, > + struct drm_display_mode *adjusted_mode); > void drm_calc_timestamping_constants(struct drm_crtc *crtc, > const struct drm_display_mode *mode); > wait_queue_head_t *drm_crtc_vblank_waitqueue(struct drm_crtc *crtc); > -- > 2.56.0 > -- Ville Syrjälä Intel ^ permalink raw reply [flat|nested] 14+ messages in thread
end of thread, other threads:[~2026-10-30 16:06 UTC | newest] Thread overview: 14+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-09-14 10:57 [PATCH v4] drm/vblank: Don't arm vblank timer with invalid frame duration Thomas Zimmermann 2026-09-14 11:11 ` sashiko-bot 2026-10-28 7:29 ` Thomas Zimmermann 2026-10-28 12:15 ` Dmitry Osipenko 2026-10-28 14:54 ` Ville Syrjälä 2026-10-29 11:35 ` Thomas Zimmermann 2026-10-30 12:07 ` Ville Syrjälä 2026-10-30 13:04 ` Thomas Zimmermann 2026-10-30 13:48 ` Ville Syrjälä 2026-10-30 14:25 ` Thomas Zimmermann 2026-10-30 15:33 ` Thomas Zimmermann 2026-10-30 15:48 ` sashiko-bot 2026-10-30 15:58 ` Thorsten Leemhuis 2026-10-30 16:06 ` Ville Syrjälä
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.