Skip to content

drm: extend hrtimer vblank pacing with update gating and max_fps - #594

Open
xwr-ron wants to merge 3 commits into
DisplayLink:mainfrom
xwr-ron:feature/vblank-update-pacing
Open

xwr-ron wants to merge 3 commits into
DisplayLink:mainfrom
xwr-ron:feature/vblank-update-pacing

Conversation

@xwr-ron

@xwr-ron xwr-ron commented Sep 12, 2026 •

Copy link
Copy Markdown

Summary

This PR builds on and extends #562 by @IraSkyx.

The original hrtimer-based vblank commit is included with its original
authorship and cherry-pick attribution. On top of that work, this PR:

  • gates userspace frame acquisition to at most one permitted update per
    vblank;
  • handles the REQUEST_UPDATE fast path that can otherwise bypass
    UPDATE_READY;
  • dispatches pending painter notifications from workqueue context instead
    of hrtimer context;
  • adds an optional writable max_fps limit without changing the DRM mode
    or vblank cadence.

max_fps=0 preserves the native update cadence.

Motivation

I started investigating consistently high CPU usage in
DisplayLinkManager during fullscreen video playback on a 1920x1080@60
DisplayLink output.

#562 significantly improved the situation by introducing hrtimer-based
vblank delivery and proper DRM frame pacing.

However, userspace frame acquisition could still proceed at the full
display refresh rate.

Tracing evdi_painter_grabpix_ioctl showed approximately one GRABPIX
operation per display refresh.

The important second path turned out to be
evdi_painter_request_update_ioctl: when dirty rectangles are already
available, REQUEST_UPDATE can complete immediately, bypassing
UPDATE_READY.

This PR adds explicit update-slot gating to cover both paths.

Implementation

The vblank timer publishes a frame token for each permitted update slot.

The immediate REQUEST_UPDATE dirty-data path atomically consumes that
token. If the current slot has already been consumed, the request remains
pending until a later slot.

Pending UPDATE_READY notification is dispatched through workqueue
context rather than from the hrtimer callback, keeping painter locking
out of timer context.

For kernel API compatibility, the hrtimer enable/disable implementation
is shared by both the modern CRTC vblank callbacks and the legacy
drm_driver vblank callbacks.

The optional max_fps parameter uses a fractional accumulator against
the active refresh rate. This permits rates that are not integer
divisors of the display refresh.

For example, on a 60 Hz mode:

  • max_fps=45 permits three update slots for every four vblanks;
  • max_fps=30 permits one update slot for every two vblanks.

The CRTC mode and DRM vblank cadence are left unchanged.

Runtime results

Test output:

  • DisplayLink output: 1920x1080@60
  • Kernel: Linux 7.2.4-arch1-2
  • fullscreen 1080p60 video workload
  • GRABPIX measured with bpftrace
  • DisplayLinkManager CPU measured from /proc/<pid>/stat
max_fps GRABPIX rate DisplayLinkManager CPU DRM mode
0 60.00 fps 58.00% 1920x1080@60
60 59.93 fps 58.33% 1920x1080@60
45 44.93 fps 42.87% 1920x1080@60
30 30.00 fps 30.93% 1920x1080@60

The measurements show that frame acquisition follows the configured
limit while the physical output remains at 60 Hz.

The CPU figures are workload- and system-specific and are included to
show the observed behavior rather than as a general performance
benchmark.

Boot / DisplayLink test

The final tree was also tested through a cold boot with max_fps=45
configured through modprobe options.

After boot:

  • the expected EVDI module was loaded;
  • DisplayLinkManager started normally;
  • the DisplayLink device reconnected successfully;
  • EDID/modeset completed successfully;
  • the external output was available in Hyprland at 1920x1080@60;
  • the runtime max_fps value was correctly set to 45.

Build compatibility

The final module was successfully built against:

  • Linux 7.2.4-arch1-2
  • Linux 6.18.51-1-lts
  • Linux 6.18.9-arch1-1-g14
  • Linux 5.10, exercising the legacy DRM vblank callback path

The 6.18.9 custom-kernel build emitted only the expected compiler-version
warning because that kernel had originally been built with an older GCC.

The Linux 5.10 build used a vanilla kernel source tree. Preparing that
older tree with the current GCC required an unrelated host-tool
compatibility fix in tools/lib/subcmd; after kernel preparation, the
EVDI module itself built successfully through the legacy API path.
Feature detection did not select the modern CRTC atomic-state/commit
callback variants for that build.

Relationship to #562

This work directly builds on the hrtimer vblank implementation introduced
in #562 by @IraSkyx.

Because #562 is still open, its commit is preserved here with the original
author and cherry-pick attribution.

If the maintainers prefer #562 to land independently first, I am happy to
rebase this PR on top of it or split the follow-up commits as appropriate.

IraSkyx and others added 3 commits September 13, 2026 03:27
REQUEST_UPDATE can complete immediately when dirty rectangles are
already pending, bypassing the UPDATE_READY notification path. This
allows userspace frame acquisition to proceed independently of the
DRM vblank cadence.

Introduce per-device update state driven by the vblank timer. A frame
token is published for each permitted vblank slot and consumed by the
immediate REQUEST_UPDATE path. If the current slot has already been
consumed, the request remains pending until a later slot.

UPDATE_READY notification is dispatched through workqueue context
rather than directly from the hrtimer callback, keeping painter locking
out of timer context.

Use the same hrtimer implementation from both modern CRTC vblank
callbacks and the legacy drm_driver vblank callbacks so the pacing path
also remains available on older supported kernels.
Add a writable max_fps module parameter for limiting userspace frame
acquisition without changing the DRM mode or vblank cadence.

A fractional accumulator derives permitted update slots from the active
refresh rate, allowing non-divisor rates such as 45 FPS on a 60 Hz
mode.

max_fps=0, or a value greater than or equal to the active refresh rate,
preserves native update cadence.
@xwr-ron
xwr-ron force-pushed the feature/vblank-update-pacing branch from 3e6c4d5 to 6cb6552 Compare September 13, 2026 00:12
@xwr-ron
xwr-ron marked this pull request as ready for review September 13, 2026 00:16

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants